Skip to content

fix: keyring calls panicked when made from inside the async runtime - #34

Merged
dmaax merged 1 commit into
mainfrom
fix/keyring-inside-runtime
Aug 24, 2026
Merged

dmaax merged 1 commit into
mainfrom
fix/keyring-inside-runtime

Conversation

@dmaax

@dmaax dmaax commented Aug 24, 2026

Copy link
Copy Markdown
Owner
$ mikrotui host migrate
thread 'main' panicked at tokio/src/runtime/scheduler/multi_thread/mod.rs:91:9:
Cannot start a runtime from within a runtime.

Reported from real use. The Secret Service backend bridges its async D-Bus client into the synchronous keyring API with block_on, and tokio refuses that on one of its worker threads. main is #[tokio::main], so every keyring call was on one.

My reasoning was wrong

I picked the rt-tokio-crypto-rust feature to "reuse the Tokio runtime MikroTUI already has". What that feature actually does is make the backend use tokio — and calling tokio's block_on from inside tokio is precisely what panics.

Migrate is only where it surfaced

Ten call sites reach the keyring, all inside the runtime:

  • the three host subcommands
  • password resolution at startup
  • App::switch_host — this one would have killed a live TUI session on a keypress, with a router already connected

The fix

Keyring work runs on a thread of its own, done inside the backend rather than at each call site so a new caller cannot forget it — and so it holds whichever store feature is selected, instead of depending on one backend's internals. A thread per call is affordable: these are rare and already wait on D-Bus.

Why the tests missed it

Reproducing needs a config that actually has an obfuscated password — without one, migrate returns before touching the keyring. My first two attempts to reproduce printed "nothing to migrate" and looked fine.

The regression test calls all three entry points from inside #[tokio::test]. Reverted, it fails with the original panic:

Cannot start a runtime from within a runtime
test result: FAILED

Verified end to end afterwards: host migrate moved a password and host list reported 🔐 keyring.

106 tests.

🤖 Generated with Claude Code

`mikrotui host migrate` aborted with "Cannot start a runtime from within a
runtime" as soon as there was actually a password to migrate.

The Secret Service backend bridges its async D-Bus client into the synchronous
keyring API with `block_on`, and tokio refuses that on one of its worker
threads. `main` is `#[tokio::main]`, so every keyring call was on one.

The reasoning that put it there was mine and it was wrong: the
`rt-tokio-crypto-rust` feature was chosen to "reuse the Tokio runtime MikroTUI
already has", but what that feature does is make the backend *use* tokio, and
using tokio's block_on from inside tokio is exactly what panics.

Migrate is only where it surfaced. Ten call sites reach the keyring, and all
of them run inside the runtime: the `host` subcommands, password resolution at
startup, and `App::switch_host` — which would have taken a live TUI session
down mid-use, on a keypress, with a router already connected.

Keyring work now runs on a thread of its own. That is done inside the backend
rather than at each call site, so a new caller cannot forget it, and it holds
whichever store feature is selected instead of depending on one backend's
internals. A thread per call is affordable: these are rare and already wait on
D-Bus.

Reproducing it needed a config that actually has an obfuscated password —
without one, migrate returns before touching the keyring, which is why the
existing tests never saw it. The regression test calls all three entry points
from inside `#[tokio::test]`, and fails with the original panic when the fix
is reverted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@dmaax
dmaax merged commit 4ea16e9 into main Aug 24, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant