DEV Community

Cover image for The Bug I Shipped, Then Had to Fix Myself: Disabling proton-pass-cli's Keyring on Android
Gouranga Das Samrat
Gouranga Das Samrat

Posted on

The Bug I Shipped, Then Had to Fix Myself: Disabling proton-pass-cli's Keyring on Android

A little over a week after proton-pass-cli merged into termux-packages, I was still riding the high of seeing my name next to Maintainer:. Then someone actually used the package the way a normal person would — fresh install, no tribal knowledge, straight to pass-cli login — and it broke.

That's the moment a package stops being "done" and starts being maintained.

The comment that started it

It came in on the original PR thread, from @EvanTechDev, the same person who'd cheered the package on back when it was just an issue:

"Hi there! Is this a bug?" — with a screenshot of pass-cli login blowing up.

The error underneath the screenshot was this:

Error: Error creating client features
Caused by:
    0: Failed to get encryption key for database
    1: Could not get local key from keyring
    2: Error accessing credential [...]: NoDefaultStore
Enter fullscreen mode Exit fullscreen mode

My first reply was the fast one, the kind you type from your phone between other things:

export PROTON_PASS_KEY_PROVIDER=fs
Enter fullscreen mode Exit fullscreen mode

That workaround was correct, and it unblocked Evan. But a one-line env var you have to know to paste into your shell config isn't a fix — it's a rumor that gets passed around in comment threads. I opened PR #31137 — fix(main/proton-pass-cli): disable keyring provider on Android to actually close the gap.

Why it was broken in the first place

pass-cli's default key storage, PROTON_PASS_KEY_PROVIDER=keyring, doesn't talk to D-Bus or Secret Service at all — it goes straight for the Linux kernel keyring, via the linux_keyutils crate. Android's kernel doesn't expose that the way a normal desktop Linux kernel does, so the backend fails to even initialize. NoDefaultStore isn't a config problem, it's a "this API doesn't exist here" problem.

And installing gnome-keyring doesn't rescue you, either — pass-cli only speaks to a Secret Service provider like GNOME Keyring when you explicitly set PROTON_PASS_LINUX_KEYRING=dbus. There's no automatic fallback. On Termux, the default provider was simply never going to work, for anyone, out of the box.

Two philosophies, one thread

I opened the PR with a shim: intercept the env var, force it to something sane before pass-cli ever saw the broken default. It was the smaller diff.

@robertkirkman, who'd reviewed nearly every line of the original proton-pass-cli PR too, wasn't on board:

"I prefer not to use this solution. Please patch the Rust code to completely erase PROTON_PASS_KEY_PROVIDER=keyring as an option from the codebase, forcing the default to something else that way."

Meanwhile @TomJo2000 had actually been the one who nudged me toward the shim approach in the first place, drawing a comparison to how Termux handles just's env var. So I had two respected reviewers, two different directions, and I did the only sane thing: I stopped coding and asked them to agree with each other first.

They did — and TomJo2000 talked himself out of his own suggestion in the process:

"If keyring is entirely broken then removing the codepath is probably the better solution. [...] the difference being here that the options form a set and the default selection doesn't work on Android. Whereas just provides its env var as an additional configuration mechanism with no default, and we set it to a sane value for our context."

That distinction stuck with me. A shim makes sense when you're adding a knob Termux needs. It doesn't make sense when the knob already exists and one of its settings is just categorically broken on the platform — at that point you patch the source, not the environment around it.

Patching Rust, then patching it properly

So I went into pass-cli/src/features/mod.rs and rewrote the provider match: an empty key provider ("") now maps to fs, and anyone who explicitly asks for keyring gets a clear error explaining that the kernel keyring is unsupported on Termux/Android, instead of a cryptic NoDefaultStore three layers deep in a stack trace.

First pass, I did it as inline sed edits in build.sh. Robertkirkman flagged that immediately — three separate instances of the same file path, code being replaced with no context preserved, and lines getting long inside build.sh itself. He also mentioned, not unkindly, that I'd never actually written a .patch file for a Termux package before and offered to walk me through it if I needed it. I moved everything into a proper patch file instead, per the contributing guide's instructions on creating patch files.

Even inside the patch file, the review kept sharpening things. Robertkirkman spotted that my new "" case and the existing "fs" case were doing the same thing and could just be combined:

"fs" | "" => { /* filesystem-based local key provider */ }
Enter fullscreen mode Exit fullscreen mode

I made the change, pushed what I thought was the update — and then sat through a slightly awkward round where he was still looking at the old version because I'd merged locally but hadn't actually pushed yet. Nothing dramatic, just the very normal rhythm of an async review: confirm, fix, confirm again.

The last review comment wasn't even about my patch — it was about the older 0001-fix-protoc-path.patch from the original PR. Robertkirkman asked me to preserve a short comment above --- a/pass-domain/build.rs explaining that the patch exists to point the build at the protoc binary from termux_setup_protobuf, so the next person reading the patch file doesn't have to reconstruct why it exists from scratch.

Where it landed

By the end there were two clean patch files sitting in packages/proton-pass-cli/: the existing protoc path fix, now with context preserved at the top, and a new 0002-disable-android-keyring.patch that removes the broken default at the source instead of working around it downstream.

Robertkirkman closed the thread with the kind of message that makes the whole review cycle worth it:

"Thank you for fixing it!"

What actually changed

  • pass-cli login now works on a fresh Termux install, no env var required, no tribal knowledge to pass around in comment threads.
  • Anyone who does explicitly reach for PROTON_PASS_KEY_PROVIDER=keyring gets told exactly why it won't work on Android, instead of NoDefaultStore.
  • The fix lives in a real .patch file with context preserved, which was a first for me on this project — and something I now know how to do for the next bug.
  • The original shim idea didn't survive review, and that's fine. Getting talked out of the smaller diff by two people who understood the platform better than I did is exactly what review is for.

The part that keeps repeating

This is the second time in a month that shipping a Termux package taught me more than writing it did. First it was a broken symlink chain in sqlcipher that nobody had ever tripped over. This time it was a keyring backend that simply doesn't exist on Android, hiding behind an env var nobody knew to set. Neither bug showed up until a real person ran a real command and hit a wall.

Maintaining a package, it turns out, is mostly this: someone posts a screenshot, and you go find out why.

If you want the trail:

And if this is the first you're hearing of any of it:

pkg install proton-pass-cli
Enter fullscreen mode Exit fullscreen mode

It'll actually log in now.

Top comments (0)