fix(auth): complete Safari sign-in handoffs

AU
11d ago
+321-15

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:

  1. 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.
  2. 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.
  3. 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):
    • cancelledBrowserCompletionAcceptsLateSafariCallback on 1cefdaa8 (test only): app signed out, tokens nil.
    • browserCancellationDuringExternalCallbackValidationKeepsSignIn on a057cfe3: attempt.value false.
    • browserCancellationWhileExternalCallbackSeedsTokensKeepsSignIn and retriedSignInKeepsTheReplacedAttemptsSafariCallbackValid on a scratch commit (a057cfe3 plus the new tests, d15acd44): app signed out, tokens cleared. This is the issue's symptom.
    • replacedAttemptsSafariCallbackSignsInWhileTheRetryIsStillOpen on 94b68eb4: callback rejected.
  • Green on c6312d94: every HostBrowserSignInFlow* suite and HostBrowserSignOutTests pass, including successfulSignInRetiresOtherKeptCallbackStates.
  • 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: everyCuratedSettingEntryIsReachable fails on setting:automation:chief-dangerous-permissions. That entry is unchanged from main; this PR does not touch settings search.
  • Fleet worker note: KeychainStackTokenStoreTests.clearingLegacyTokensPreservesSameAccountInAnotherService failed on aws-m4pro-2 with SecItemAdd → -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.