mobile: relay pairing recovery strands on "recovery pending" after desktop rotates QR
- Dominant language
- TypeScript
- Stars
- 71.3k
- Forks
- 4.7k
- Avg merge
- 14h 54m
- Merged PRs (30d)
- 520
Description
## Describe the bug
When a mobile pairing attempt fails after the E2EE handshake but before provisioning completes, the desktop-generated pairing journal is left in AsyncStorage with `winner` set. If the user then regenerates the QR on the desktop (which the desktop responds to by revoking the previous device's relay binding via `queueRelayDeviceRevoke`), every credential the journal holds becomes permanently invalid. The next phone scan is blocked by `saveMobileRelayPairingJournal`'s guard with the error `"mobile relay pairing recovery pending"`, and the journal never clears until `inviteExpiresAt + 10min grace` elapses (~20 minutes from the original QR mint).
## Reproduction
1. Desktop Orca → Settings → Mobile → generate QR (mode: Anywhere)
2. Phone scans QR; relay path is unreachable (e.g. phone not yet on VPN, desktop control socket RST'd)
3. The phone gets far enough to set `winner` on the journal, then fails at `pairing.provisionRelay`
4. Desktop regenerates the QR (or the user toggles connection mode) — `runtime-rpc.ts:807-816` revokes the previous device's relay binding
5. Phone scans the new QR — `saveMobileRelayPairingJournal` throws `"mobile relay pairing recovery pending"`
6. Force-quit + relaunch the phone app: `recoverMobileRelayPairing` runs, but the cell returns `4401 BAD_OUTER_CREDENTIAL` for every credential and the catch block at `mobile-relay-pairing-recovery.ts:144` swallows it as if it were a transient transport error → returns `'deferred'` → journal stays → next scan still blocked
## Expected behavior
Recovery should abandon the journal as soon as every credential is authoritatively rejected (`4401`), so a fresh scan can proceed immediately instead of waiting the full invite TTL + grace window.
## Environment
- Orca mobile app
- Orca desktop main @ recent
- Pairing mode: Anywhere (relay)
- Observed in a network environment where the relay cell is intermittently reachable from the phone
## Diagnosis
`mobile/src/transport/mobile-relay-pairing-recovery.ts:144` `catch {}` treats every error the same. It should distinguish `RelayOuterError(BAD_OUTER_CREDENTIAL)` from transient failures and abandon when all credentials are permanent.
Contributor guide
Research direction
Start in mobile/src/transport/mobile-relay-pairing-recovery.ts around line 144 and trace recoverMobileRelayPairing's handling of RelayOuterError(BAD_OUTER_CREDENTIAL), alongside the revocation flow in runtime-rpc.ts:807-816. Verify that all authoritative credential rejections abandon the journal while transient failures remain deferred, allowing a fresh scan immediately.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- mobile, security
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 72/100