fix(auth): complete Safari sign-in handoffs
Summary
Fixes https://github.com/manaflow-ai/cmux/issues/20280: sign-in finished in Safari, but the app stayed signed out and showed no error.
When Safari hands the callback to cmux through LaunchServices, the hosted browser session can also report "cancelled" around the same moment. Three paths dropped that valid callback without saying anything:
- Cancel before the callback arrives. The cancel ended the attempt and dropped its callback state. The callback then failed as
unknownOrExpiredState, with no failure recorded. Now the attempt's state stays valid for single use, for as long as an abandoned attempt would live. - Cancel while the callback is still being processed. Resuming the session ended the attempt mid-way. During token seeding, the callback then cleared its own tokens as "raced". Later in processing, the awaited
signIn(Settings progress,auth.begin_sign_in) still reported failure. Now the external callback holds the session continuation until it finishes. - The user clicks Sign In again because nothing happened. The new attempt discarded the first attempt's state, so finishing the first Safari tab was dropped. Now a replaced attempt's state stays valid for single use, including while the retry's popup is still open (the first tab signs in and ends the retry). A successful sign-in retires every other kept state, so an older tab cannot switch accounts unasked. Sign-out and timeout still clear every state, and the existing late-callback-after-sign-out rejection is unchanged.
The Settings account card now shows the localized failure title and recovery text after a failed sign-in. It hides them while sign-in is still running or a rate-limited callback is retrying.
The earlier web change (no native callback when the access token is empty) is dropped. It sent the browser to the home page while the app kept waiting. The app already rejects that callback and shows an invalid-link failure, which the new Settings row now displays.
Testing
Focused command (fleet, cmux-ci run --class light --script scripts/ci/run-swift-testing-suites.sh --arg Packages/Shared/CmuxAuthRuntime):
- Red, before each fix (
--red, only the new tests):cancelledBrowserCompletionAcceptsLateSafariCallbackon1cefdaa8(test only): app signed out, tokens nil.browserCancellationDuringExternalCallbackValidationKeepsSignInona057cfe3:attempt.valuefalse.browserCancellationWhileExternalCallbackSeedsTokensKeepsSignInandretriedSignInKeepsTheReplacedAttemptsSafariCallbackValidon a scratch commit (a057cfe3plus the new tests,d15acd44): app signed out, tokens cleared. This is the issue's symptom.replacedAttemptsSafariCallbackSignsInWhileTheRetryIsStillOpenon94b68eb4: callback rejected.
- Green on
c6312d94: everyHostBrowserSignInFlow*suite andHostBrowserSignOutTestspass, includingsuccessfulSignInRetiresOtherKeptCallbackStates. - Review findings and resolution: https://github.com/manaflow-ai/cmux/pull/20364#issuecomment-6305946577
Observed / Not run
- Observed: the fleet runs above.
python3 scripts/verify-local.py: 5/5 selected checks passed (swift-syntax, localization-defaults, wire-app-sources, package-groups, feature-flags). CmuxSettingsUI:everyCuratedSettingEntryIsReachablefails onsetting:automation:chief-dangerous-permissions. That entry is unchanged frommain; this PR does not touch settings search.- Fleet worker note:
KeychainStackTokenStoreTests.clearingLegacyTokensPreservesSameAccountInAnotherServicefailed on aws-m4pro-2 withSecItemAdd→-61(errSecWriteDenied). That is a keychain-access problem on the worker. This PR does not touch that file. - Not run: a live Safari sign-in against production. The visible row is captured by the new dogfood tour
account-sign-in-failure-tour.
Dogfood-tours: account-sign-in-failure-tour
Changelog
- Fixed: Signing in through Safari could leave cmux signed out after authentication completed.
🤖 Generated with Claude Code
Summary by CodeRabbit
-
Bug Fixes
- Improved browser sign-in reliability when callbacks arrive after cancellation, during retries, or while authentication data is being saved.
- Preserved valid sign-in callbacks across replacement attempts and completed authentication more consistently.
- Retired outdated callback states after successful sign-in.
-
New Features
- Added an account sign-in failure message with localized title, recovery guidance, warning indicator, and accessibility support.
- Sign-in failure messaging is hidden while a rate-limit retry is active.
-
Tests
- Added coverage for delayed callbacks, retries, cancellation, token saving, and the account sign-in failure experience.