fix(setup): stop panicking on /setup/recover when client is cached #69

Merged
jlxq0 merged 1 commit from fix/setup-recover-double-init into main 2026-05-18 11:50:58 +00:00
Owner

Summary

/setup/recover was crashing the pod with AlreadyInitializedError(Matrix) when claude.ai had already built the matrix-sdk client for the user (which the /setup precondition demands). The recover handler called MatrixClientCache::for_user(&identity, &session.access_token), where session.access_token is /setup's own unbound OAuth grant — a different token than the one claude.ai installed on the client. The fast path saw mismatched hashes and tried restore_session to swap. matrix-sdk 0.17 rejects double-initialisation → panic → pod crash.

Observed live on v0.3.3 during device-binding recovery testing on 2026-05-18: after MATRIXMCP2 was signed out and the claude.ai connector re-OAuthed, a list_joined_rooms built the client at 11:38:20 and a /setup/recover POST at 11:38:48 tripped the panic.

Fix

  • New MatrixClientCache::get_if_cached(&mxid) -> Option<Arc<Client>> that returns the cached client untouched (no restore_session, no token swap).
  • /setup/recover now routes through get_if_cached instead of for_user. The recovery runs against Synapse using claude.ai's already-installed device-bound bearer — which is what we actually want, since the unbound /setup token has no device scope.
  • Removed the misleading comment claiming for_user silently drops /setup's token. It didn't.
  • SetupSession::access_token and SetupSession::mas_subject marked #[allow(dead_code)] with comments — still populated at callback time, no longer routed into the cache. Kept on the session shape for rolling deploys.

Test plan

  • cargo fmt --check clean
  • cargo clippy --all-targets -- -D warnings clean
  • cargo test --all-targets 125/125 green
  • Post-deploy: sign out the new MATRIXMCP device → reconnect at claude.ai → run a tool → POST /setup/recover with the recovery key → expect success (not pod crash)

Compat

Server-only change; no MCP surface affected. No client reconnect required.

## Summary `/setup/recover` was crashing the pod with `AlreadyInitializedError(Matrix)` when claude.ai had already built the matrix-sdk client for the user (which the `/setup` precondition demands). The recover handler called `MatrixClientCache::for_user(&identity, &session.access_token)`, where `session.access_token` is `/setup`'s own unbound OAuth grant — a different token than the one claude.ai installed on the client. The fast path saw mismatched hashes and tried `restore_session` to swap. matrix-sdk 0.17 rejects double-initialisation → panic → pod crash. Observed live on **v0.3.3** during device-binding recovery testing on 2026-05-18: after `MATRIXMCP2` was signed out and the claude.ai connector re-OAuthed, a `list_joined_rooms` built the client at 11:38:20 and a `/setup/recover` POST at 11:38:48 tripped the panic. ## Fix - New `MatrixClientCache::get_if_cached(&mxid) -> Option<Arc<Client>>` that returns the cached client **untouched** (no `restore_session`, no token swap). - `/setup/recover` now routes through `get_if_cached` instead of `for_user`. The recovery runs against Synapse using claude.ai's already-installed device-bound bearer — which is what we actually want, since the unbound `/setup` token has no device scope. - Removed the misleading comment claiming `for_user` silently drops `/setup`'s token. It didn't. - `SetupSession::access_token` and `SetupSession::mas_subject` marked `#[allow(dead_code)]` with comments — still populated at callback time, no longer routed into the cache. Kept on the session shape for rolling deploys. ## Test plan - [x] `cargo fmt --check` clean - [x] `cargo clippy --all-targets -- -D warnings` clean - [x] `cargo test --all-targets` 125/125 green - [ ] Post-deploy: sign out the new MATRIXMCP device → reconnect at claude.ai → run a tool → POST /setup/recover with the recovery key → expect success (not pod crash) ## Compat Server-only change; no MCP surface affected. No client reconnect required.
fix(setup): stop panicking on /setup/recover when client is cached
All checks were successful
CI / cargo (pull_request) Successful in 49s
CI / docker (pull_request) Successful in 1m6s
f747b55dc1
`/setup/recover`'s precondition demands the cached matrix-sdk client
exists for the user (built by claude.ai's connector via a prior tool
call). The recover handler then called
`MatrixClientCache::for_user(&identity, &session.access_token)`, where
`session.access_token` is `/setup`'s own (unbound) OAuth grant — a
different access token than the one claude.ai installed on the client.
The fast path saw mismatched token hashes and tried to swap via
`restore_session`. matrix-sdk 0.17 rejects double-initialisation with
`AlreadyInitializedError`, panicking the tokio worker and crashing
the pod.

Observed live on 2026-05-18 v0.3.3 during device-binding recovery
testing: after MATRIXMCP2 was signed out and the claude.ai connector
re-OAuthed, a `list_joined_rooms` call built the client at 11:38:20
and a `/setup/recover` POST at 11:38:48 tripped the panic. The
misleading comment ("`/setup`'s own unbound access_token is silently
dropped by the fast path") was a lie — it wasn't.

Fix: add `MatrixClientCache::get_if_cached(&mxid) -> Option<Arc<Client>>`
that returns the cached client without touching its session, and route
`/setup/recover` through it. The recovery operation runs against
Synapse using claude.ai's already-installed device-bound bearer (what
we actually want; the unbound `/setup` token has no device scope).

Also mark `SetupSession::access_token` and `SetupSession::mas_subject`
as `#[allow(dead_code)]` with a comment — they're still populated at
callback time but no longer routed into the cache. Kept on the
session shape for rolling deploys and future flows.

Tests: 125/125 still green. cargo fmt + clippy strict both clean.
jlxq0 merged commit 1e9e7683a9 into main 2026-05-18 11:50:58 +00:00
Sign in to join this conversation.
No description provided.