auth: the pricing page's sign-in completes during an open attempt; log the Stack code behind auth_failed
@austinpower1258 this one is yours to call. It changes classic sign-in, so it carries needs a call and nobody merges it without you.
Two sign-in failures Leo hit on classic NIGHTLY 69d0e13:
(a) Upgrade ā sign in ā "Return to cmux" ā "Couldn't finish sign-in." The pricing page's Sign in hands back a stateless callback through the embedded browser (.trustedEmbeddedBrowser). With no sign-in attempt open, that handoff is trusted. With a native attempt still open, handleCallbackURL compared its missing state with the attempt's state, rejected it as stateMismatch and returned false. The embedded browser then showed the alert. Its title reuses account.signIn.error.unknown.title, which is why it read as Failure.unknown. The rejection is logged at debug level, which is not persisted, so Leo's log had no line for it.
Fix: a stateless callback from the trusted embedded browser completes through the open attempt and ends it, like a matching callback does. Every other callback must still match the attempt's state, including a stateless one from outside the app.
(b) The first plain Sign in says "temporarily unavailable" and the second works. Leo's log shows it twice: at 04:54Z and 06:33Z. Each time, auth.callback completion failed: serverError(0, "auth_failed") came about 225 ms after the callback, and a retry about 10 s later succeeded. The 04:54Z failure was before #20041 reached production. It is not a Stack 429, because AuthError(displaySafe:) passes RATE_LIMITED through unchanged. serverError(0, "auth_failed") stands for any Stack code the mapping does not list, or any error that is not a URL error. That hides the actual cause.
This PR does not guess at that cause. completeExternalSignIn now logs auth.externalSignIn validation failed: stack_error=<code> type=<type> (or the domain and code of an error that is not from Stack) at error level before mapping it. The next repro then names the code. The server's message is never logged.
(b), follow-up. The coordinator traced Leo's exact sequence: a Sign in whose "open cmux?" prompt was cancelled, then a second Sign in that fails, then a third that works. The Stack SDK turns any /users/me failure, an anonymous user and a restricted user into the same USER_NOT_SIGNED_IN. The Stack-code line alone therefore cannot tell them apart. The failure line now also logs the handed-back access token's own is_anonymous, is_restricted and expiry claims, read locally from the token: booleans only, never the token. The suspected cause is on the web side: after-sign-in hands the app an anonymous browser session. The fix for that is #20242, also needs a call. Test: HostBrowserSignInFlowAccessTokenClaimsTests. Its red (df8a930) is compile-only because the helper is new, and it was pushed together with the fix, so there is no separate red run.
Tests, red before the fix (commit 33d1c4137e) and green after:
HostBrowserSignInFlowCallbackTrustTests.trustedEmbeddedStatelessCallbackCompletesDuringActiveAttemptHostBrowserSignInFlowCallbackTrustTests.externalStatelessCallbackDuringActiveAttemptIsRejected(guard; it passes before and after)AuthErrorDisplaySafeTests.diagnosticNamesTheStackCodeTheDisplayErrorHidesanddiagnosticNamesANonStackErrorsDomainAndCode
The Swift was not built locally (Linux). CI's package tests and the auth lifecycle job run these suites.
Changelog
Fixed: Signing in from the Upgrade page no longer ends with "Couldn't finish sign-in" while another sign-in is open.
š¤ Generated with Claude Code
Need help on this PR? Tag @codesmith-bot with what you need.
Summary by cubic
Fixes two sign-in failures in the classic flow. Signing in from the pricing page no longer ends with "Couldn't finish sign-in" when a native sign-in attempt is already open: a stateless callback from the trusted embedded browser now completes through the open attempt and ends it, bypassing both state checks, while any other callback must still match the attempt's state. External sign-in validation failures now log the underlying Stack error code, or the domain and code of a non-Stack error, before being mapped to serverError(0, "auth_failed").
- A failed hand-back's log line also names the account its token was minted for ā anonymous, restricted, or expired ā read from the token's claims, since the SDK reports all three as
USER_NOT_SIGNED_IN.