web: after-sign-in sends the app's sign-in back to finish instead of handing it an anonymous session
@austinpower1258 FYI. This is the web half of #20234 bug (b) and needs Lawrence's yes before it lands, so it is labelled needs a call. Do not merge.
Leo's repro, every time: (1) Sign in, the browser signs in, and Leo cancels the browser's "open cmux?" prompt. (2) Sign in again and accept: the app fails about 225 ms later with serverError(0, "auth_failed"). (3) Sign in a third time: it works.
Likely cause (not yet proven):
after-sign-inmints a fresh session from whatever the browser holds at that moment. That includes an anonymous user (anonymous-if-exists), and it hands that session to the app.- The app's validation uses
getUser(or: .throw). That rejects an anonymous user asUSER_NOT_SIGNED_IN, which the app shows asauth_failed. - Prod logs at 06:33Z fit, matched by timing because they carry no user ID. The failing callback (06:33:10.5) follows an
after-sign-inat 06:33:03, before that browser'soauth-callbackat 06:33:06. The working one (06:33:23) follows theafter-sign-inat 06:33:22. - #20234 now logs the token's
is_anonymousandis_restrictedclaims on that failure, so the next repro can confirm or rule this out.
Fix: when the app's own Sign in (a native return carrying cmux_auth_state) reaches after-sign-in and the browser holds only an anonymous session, the browser is sent back to /handler/sign-in with the same return, including the handoff nonce. It is no longer handed a session the app will refuse. Purchase returns carry no state, so the anonymous purchaser's session is unchanged (the existing test still passes).
Not covered: a restricted user, or no user with cookie tokens. That path still hands back cookie tokens, because many existing tests rely on it. If the next repro's log shows is_restricted=true, that path needs the same treatment.
Not proven: whether Stack's sign-in page shows the form, or redirects again straight away, when it sees an anonymous session. This needs a live check before landing.
Tests, in web/tests/after-sign-in-route.test.ts:
sends the app's sign-in back to finish when the browser holds only an anonymous session: red at b7c35c8 (it handed the anonymous tokens to the app), green at eff0513.still hands a full account's session to the app's sign-in: a guard.- The full file is 27 pass.
bun run lint:complexity, eslint and tsc are clean.
Changelog
Fixed: Signing in to cmux again after cancelling the browser's "open cmux?" prompt no longer fails with "temporarily unavailable".
š¤ Generated with Claude Code
Need help on this PR? Tag @codesmith-bot with what you need.
Summary by cubic
When a Sign in from the app reaches after-sign-in while the browser holds only an anonymous session, after-sign-in now redirects back to /handler/sign-in to finish the browser's sign-in instead of handing the app tokens the app rejects as not signed in.
Bug Fixes
- Fixes #20234, where signing in again after cancelling the browser's "open cmux?" prompt failed with "temporarily unavailable" about 225 ms later.
- Purchase returns still keep the anonymous purchaser's session, since they carry no auth state.
- A restricted user, or no user with cookie tokens, still goes down the existing path that hands back cookie tokens.
Tests
- Adds tests covering the anonymous-session redirect and confirming a full account is still handed to the app.