Skip to content

Surface auth sign-in errors in Settings (#3617) - #3621

Closed
psh4607 wants to merge 3 commits into
manaflow-ai:mainfrom
psh4607:fix-auth-signin-error-visibility
Closed

psh4607 wants to merge 3 commits into
manaflow-ai:mainfrom
psh4607:fix-auth-signin-error-visibility

Conversation

@psh4607

@psh4607 psh4607 commented May 6, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Closes #3617.

AuthManager.beginSignIn and handleCallbackURL previously swallowed every failure via NSLog only. A user clicking Settings → Sign In… who hit any of ASWebAuthenticationSessionError (canceled consent, sandbox/TCC, network unreachable…), handleCallbackURL throwing .invalidCallback because the after-sign-in handler emitted a malformed deep link, or session.start() returning false saw the auth sheet flash and close with no visible error and no way to debug. The app stayed at "Not signed in" silently.

This PR publishes the most recent failure as AuthManager.lastSignInError and renders it in red below the Account row subtitle in AuthSettingsRow, so the failure mode that motivated #3617 is no longer invisible — regardless of which underlying root cause hits in any given environment.

What changed

  • Sources/Auth/AuthManager.swift
    • AuthManagerError is now Equatable and gains a signInSessionFailed case for non-AuthManagerError failures (network, sandbox, etc.).
    • New @Published private(set) var lastSignInError: AuthManagerError? published so SwiftUI can render it.
    • beginSignIn clears lastSignInError on entry, sets .signInSessionFailed on session.start() failure, and routes the completion handler's error through a new recordSignInSessionError(_:) helper that suppresses the error for ASWebAuthenticationSessionError.canceledLogin (explicit user cancel) so the UX stays quiet when the user cancels themselves.
    • handleCallbackURL sets lastSignInError = .invalidCallback before the early throw and otherwise sets it to the typed AuthManagerError (or .signInSessionFailed fallback) on any throw from tokenStore.seed / refreshSession. Clears it on success.
  • Sources/cmuxApp.swift — AuthSettingsRow reads authManager.lastSignInError?.errorDescription and renders it in red under the existing subtitle. No layout change when no error is set.
  • Resources/Localizable.xcstrings — adds settings.account.error.signInSessionFailed with en / ja / uk / ko translations, matching the existing settings.account.error.* key pattern.

This deliberately doesn't try to fix the underlying root cause(s) hypothesized in #3617 (canceledLogin from a dismissed consent dialog, double-fire from view-body re-evaluation, sandbox denial, network failure). It makes those root causes diagnosable so the next PR can target the actual cause once a Console.app log is captured.

Test plan

AGENTS.md regression test policy is two commits:

  1. e0cd032a — adds the failing regression test cmuxTests/AuthManagerSignInErrorVisibilityTests.swift. The test references AuthManager.lastSignInError, which doesn't exist yet on this commit, so CI compile fails — proving the test catches the bug.
  2. 0cbea3d8 — ships the fix. The test now compiles and asserts lastSignInError == .invalidCallback after handleCallbackURL throws on a payload-less cmux://auth-callback URL.

The CI signal across the two commits is the proof: red on commit 1, green on commit 2.

Manual verification (not run locally per AGENTS.md "Never run tests locally"):

  • Settings → Sign In… on a build where the sheet flashes-and-closes now shows Sign-in didn't complete. Please try again. under the subtitle.
  • Settings → Sign In…, dismiss the macOS consent dialog explicitly. No error is shown (we suppress canceledLogin).
  • Successful sign-in clears any prior error message.
  • Sign Out leaves no stale error message.

Out of scope (follow-up issues)

  • Actual root-cause fix for the sheet flashing-and-closing on Release v0.64.3 — needs a captured auth.webauth failed: … line from Console.app, then targeted fix per the canceledLogin / double-fire / sandbox / network branches in Settings → Sign In… opens auth sheet that closes immediately (no page rendered) #3617.
  • web/app/handler/after-sign-in/OpenNativeClient.tsx UX nit — auto-redirect to the deep link instead of relying on a manual click. Different reviewer (web), different PR.
  • Double-fire guard on AuthSettingsRow.buttonAction() (webAuthSession != nil / isLoading == true). Different concern, separate PR.

Summary by cubic

Surfaced sign-in errors in Settings so users see why auth failed instead of a silent close. Fixes #3617 by publishing the last sign-in error, showing it under Account, and clearing it on success and sign-out.

  • Bug Fixes

    • AuthManager now publishes lastSignInError; AuthManagerError is Equatable with new .signInSessionFailed. Error is set on session.start() failure, invalid callback, and other throws; suppressed for ASWebAuthenticationSessionError.canceledLogin; cleared on successful flows and on sign-out to avoid stale messages.
    • Settings shows the error message in red under the Account subtitle; no change when there’s no error.
    • Added settings.account.error.signInSessionFailed with en/ja/uk/ko translations.
    • Tests: added regression for invalid callback populating the error and another asserting sign-out clears it; tightened error matching.
  • Refactors

    • Replaced three NSLog calls in the sign-in flow with authLog (DEBUG-guarded).

Written for commit d48988d. Summary will update on new commits.

Summary by CodeRabbit

  • New Features

    • Users now see clear sign-in error messages shown in the account/auth settings row when authentication fails.
  • Localization

    • Added translations for the new sign-in error message in English, Japanese, Ukrainian, and Korean.
  • Tests

    • Added regression tests to verify sign-in error visibility and lifecycle.

psh4607 added 2 commits May 6, 2026 16:24
…ow-ai#3617)

Per AGENTS.md regression test policy: this commit ships the failing test alone so CI proves it catches the bug. The fix lands in the next commit.
AuthManager.beginSignIn / handleCallbackURL previously swallowed every failure via NSLog only, so a user who clicked Settings -> Sign In... and saw the auth sheet flash and close was left with no visible error and no way to debug.

Now AuthManager publishes the most recent failure as lastSignInError; AuthSettingsRow renders it in red below the subtitle. Suppresses the error for ASWebAuthenticationSessionError.canceledLogin (explicit user cancel) so the UX stays quiet when the user cancels themselves.

Adds a new AuthManagerError.signInSessionFailed case for non-AuthManagerError throws (network, sandbox, etc.), with a localized 'Sign-in didn't complete. Please try again.' string in en/ja/uk/ko.
Copilot AI review requested due to automatic review settings May 6, 2026 07:29
@vercel

vercel Bot commented May 6, 2026

Copy link
Copy Markdown

@psh4607 is attempting to deploy a commit to the Manaflow Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented May 6, 2026 •

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: eb0c2185-bafc-489a-a795-acbfd7c0caaf

📥 Commits

Reviewing files that changed from the base of the PR and between 0cbea3d and d48988d.

📒 Files selected for processing (2)
  • Sources/Auth/AuthManager.swift
  • cmuxTests/AuthManagerSignInErrorVisibilityTests.swift

📝 Walkthrough

Walkthrough

Adds explicit sign-in error tracking and exposure: AuthManager records lastSignInError (new published property and AuthManagerError case), sign-in flow records/clears that state, UI displays the localized error, and tests cover invalid callback visibility. A localization key for signInSessionFailed was added.

Changes

Sign-In Error Visibility Feature

Layer / File(s) Summary
Data Shape & Localization
Sources/Auth/AuthManager.swift, Resources/Localizable.xcstrings
AuthManagerError now conforms to Equatable and adds .signInSessionFailed. AuthManager gains @Published private(set) var lastSignInError: AuthManagerError?. Localization entry settings.account.error.signInSessionFailed added (en/ja/uk/ko).
Core Error Handling
Sources/Auth/AuthManager.swift
Sign-in flow updated to set/clear lastSignInError: beginSignIn sets .signInSessionFailed when web auth fails to start; web auth failures call recordSignInSessionError(_:); handleCallbackURL validates payload, sets .invalidCallback on bad input, and wraps token seeding/session refresh in do-catch to update lastSignInError on failure and clear it on success. Several success paths reset lastSignInError (seed, applySignInResult, signInWithCredential, clearSessionState).
UI Wiring
Sources/cmuxApp.swift
AuthSettingsRow adds errorText computed from authManager.lastSignInError?.errorDescription and conditionally renders a red error message below the subtitle when present.
Tests
cmuxTests/AuthManagerSignInErrorVisibilityTests.swift
New tests assert lastSignInError is nil before sign-in, becomes .invalidCallback after handling an invalid callback URL, and is cleared by signOut. Includes test doubles: NoopAuthTestClient and InMemoryTestTokenStore.*

Sequence Diagram

sequenceDiagram
    actor User
    participant UI as AuthSettingsRow
    participant Manager as AuthManager
    participant WebSession as ASWebAuthenticationSession
    participant Callback as handleCallbackURL

    User->>UI: beginSignIn()
    UI->>Manager: beginSignIn()
    Manager->>WebSession: start web auth
    alt Web session fails / canceled
        WebSession-->>Manager: failure/cancel
        Manager->>Manager: recordSignInSessionError(.signInSessionFailed)
        Manager->>Manager: lastSignInError = .signInSessionFailed
    else Web session returns callback
        WebSession-->>Manager: callback URL
        Manager->>Callback: handleCallbackURL(url)
        alt invalid callback
            Callback->>Manager: lastSignInError = .invalidCallback
            Callback-->>Manager: throw invalidCallback
        else valid callback
            Callback->>Callback: seed tokens & refresh session
            Callback-->>Manager: clear lastSignInError
        end
    end
    Manager->>UI: publish lastSignInError
    UI->>User: display errorText (if present)
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 A hop to the web, a session unmade,

red text now tells where the footing swayed.
lastSignInError hums what went wrong,
a tiny carrot of truth in the UI song.

🚥 Pre-merge checks | ✅ 12 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (12 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely describes the main change: surfacing authentication sign-in errors in the Settings UI, directly addressing issue #3617.
Description check ✅ Passed The description comprehensively covers all required template sections: Summary explains what changed and why, Testing details the regression test approach and manual verification steps, and a Checklist is included with completion status.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Cmux Swift Actor Isolation ✅ Passed lastSignInError properly isolated in @MainActor class; AuthManagerError is Sendable enum; protocols marked Sendable; UI access via @ObservedObject; callbacks explicitly @MainActor.
Cmux Swift Blocking Runtime ✅ Passed PR introduces no blocking patterns. New code uses @Published properties and simple property reads/UI rendering only.
Cmux Swift Concurrency ✅ Passed Single @Published property added to existing ObservableObject class, maintaining pattern. Not material expansion. No Dispatch queues or problematic Tasks.
Cmux Swift @Concurrent ✅ Passed AuthManager is @MainActor; async methods properly isolated. No missing @concurrent, no invalid @concurrent, correct UI isolation for lastSignInError access.
Cmux Swift File And Package Boundaries ✅ Passed PR respects Swift boundaries. AuthManager (+31 lines) and cmuxApp (+4 lines) under 250-line limit. Focused error visibility feature with no mixed responsibilities.
Cmux Swift Logging ✅ Passed All production logging uses authLog(), properly wrapped in #if DEBUG guard. Commit explicitly addresses Cubic logging check. No NSLog, print, or unguarded logging violations.
Cmux Swiftui State Layout ✅ Passed AuthManager is existing ObservableObject with @Published. PR adds one property to this existing class. Matches allowed case: existing legacy view state. No new anti-patterns detected.
Cmux Architecture Rethink ✅ Passed Single source of truth for sign-in errors owned by @MainActor AuthManager. Unified state, clear invariants. No timing patterns or lifecycle splits. Small correctness fix.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps

greptile-apps Bot commented May 6, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

Surfaces browser sign-in failures that were previously swallowed silently: AuthManager now publishes lastSignInError: AuthManagerError? and AuthSettingsRow renders it in red below the account subtitle. The change adds a new signInSessionFailed error case, suppresses user-initiated cancels, and clears the error on every sign-in-success and sign-out path.

  • AuthManager.swift: New @Published private(set) var lastSignInError with clear-on-success/sign-out wiring across all sign-in paths (handleCallbackURL, seedTokensFromCLI, applySignInResult, signInWithCredential, clearSessionState); three NSLog calls replaced with Self.authLog (debug-only).
  • cmuxApp.swift: AuthSettingsRow adds a conditional red Text bound to authManager.lastSignInError?.errorDescription; no layout change when no error is present.
  • Localizable.xcstrings / test file: New settings.account.error.signInSessionFailed key in four locales; two regression tests cover the invalid-callback and sign-out-clears-error cases.

Confidence Score: 5/5

Safe to merge; error state is cleared at every auth-state transition, user-initiated cancels are suppressed correctly, and regression tests cover the two key invariants.

All sign-in-success and sign-out paths now clear lastSignInError, the stale-error scenario from the prior review thread is resolved, and the new test suite validates both the error-set and error-cleared cases. The only open item is a non-blocking suggestion to restore production unified-log visibility for the three replaced NSLog call sites.

The three replaced NSLog sites in Sources/Auth/AuthManager.swift are now debug-only; consider whether production Console.app observability is needed before the next release.

Important Files Changed

Filename Overview
Sources/Auth/AuthManager.swift Adds lastSignInError: AuthManagerError? published property; clears it at all sign-in-success and sign-out paths; sets it on session.start() failure, invalid callback, and other throws. The three removed NSLog calls are replaced with debug-only authLog, removing production unified-log visibility for auth failures.
Sources/cmuxApp.swift Adds a conditional red Text in AuthSettingsRow.body driven by errorText (computed from authManager.lastSignInError?.errorDescription). Layout change is minimal and correctly scoped; fixedSize prevents truncation on long messages.
Resources/Localizable.xcstrings Adds settings.account.error.signInSessionFailed key with en/ja/uk/ko translations, consistent with the existing settings.account.error.* key pattern.
cmuxTests/AuthManagerSignInErrorVisibilityTests.swift Adds two regression tests: one asserting lastSignInError == .invalidCallback after a payload-less callback URL, and one asserting signOut() clears the error. Both run on @MainActor with in-memory test doubles; no production behavior impacted.

Sequence Diagram

sequenceDiagram
    actor User
    participant AuthSettingsRow
    participant AuthManager
    participant ASWebAuthSession

    User->>AuthSettingsRow: Tap "Sign In…"
    AuthSettingsRow->>AuthManager: beginSignIn()
    AuthManager->>AuthManager: lastSignInError = nil
    AuthManager->>ASWebAuthSession: session.start()

    alt session.start() returns false
        AuthManager->>AuthManager: lastSignInError = .signInSessionFailed
        AuthManager-->>AuthSettingsRow: publishes lastSignInError
        AuthSettingsRow-->>User: shows red error text
    else session starts
        ASWebAuthSession-->>AuthManager: completion(callbackURL?, error?)

        alt error == canceledLogin
            AuthManager->>AuthManager: suppress (no error set)
        else other error
            AuthManager->>AuthManager: lastSignInError = .signInSessionFailed
            AuthManager-->>AuthSettingsRow: publishes lastSignInError
            AuthSettingsRow-->>User: shows red error text
        else callbackURL received
            AuthManager->>AuthManager: handleCallbackURL(url)
            alt invalid payload
                AuthManager->>AuthManager: lastSignInError = .invalidCallback
                AuthManager-->>AuthSettingsRow: publishes lastSignInError
                AuthSettingsRow-->>User: shows red error text
            else valid payload
                AuthManager->>AuthManager: tokenStore.seed + refreshSession()
                AuthManager->>AuthManager: lastSignInError = nil
                AuthManager-->>AuthSettingsRow: isAuthenticated = true, error cleared
                AuthSettingsRow-->>User: shows signed-in state
            end
        end
    end
Loading

Reviews (2): Last reviewed commit: "Address PR review feedback (#3621)" | Re-trigger Greptile

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Surfaces previously silent authentication sign-in failures (notably ASWebAuthenticationSession failures and invalid callback URLs) to users by publishing the latest sign-in error from AuthManager and rendering it in Settings, with localized messaging and regression coverage.

Changes:

  • Add AuthManager.lastSignInError (published) and record failures from beginSignIn / handleCallbackURL, including a new .signInSessionFailed error case.
  • Render the most recent sign-in error message in AuthSettingsRow below the account subtitle.
  • Add a localization key for the generic sign-in failure and a regression test ensuring invalid callbacks populate lastSignInError.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
Sources/cmuxApp.swift Displays AuthManager.lastSignInError text in Settings (Account row) when present.
Sources/Auth/AuthManager.swift Introduces lastSignInError, new error case, and records/clears errors across sign-in and callback handling.
Resources/Localizable.xcstrings Adds localized string for settings.account.error.signInSessionFailed.
cmuxTests/AuthManagerSignInErrorVisibilityTests.swift Regression test asserting invalid callback URLs publish .invalidCallback via lastSignInError.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

if let asError = error as? ASWebAuthenticationSessionError,
asError.code == .canceledLogin {
return
}
@Published private(set) var isLoading = false
@Published private(set) var isRestoringSession = false
@Published private(set) var didCompleteBrowserSignIn = false
@Published private(set) var lastSignInError: AuthManagerError?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 New @Published state on ObservableObject — the cmux-swiftui-state-layout and cmux-swift-concurrency-modernization rules flag new @Published properties for new state when @Observable is available. AuthManager is already ObservableObject so this extends existing debt, but each new @Published widens the invalidation surface: every subscriber re-renders on every lastSignInError write. Migrating AuthManager to @Observable is the correct long-term fix.

Suggested change
@Published private(set) var lastSignInError: AuthManagerError?
// TODO: Migrate AuthManager to @Observable; adding @Published here widens
// invalidation for all subscribers. See cmux-swiftui-state-layout rule.
@Published private(set) var lastSignInError: AuthManagerError?

Rule Used: Flag SwiftUI changes that can cause stale state, b... (source)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmuxTests/AuthManagerSignInErrorVisibilityTests.swift`:
- Line 16: The test classes trigger SwiftLint's required_deinit rule; add an
explicit deinit to each test-only final class to silence the warning: locate the
final classes AuthManagerSignInErrorVisibilityTests and NoopAuthTestClient and
add an empty deinit method (deinit { }) in each class so lint passes without
changing production behavior.
- Around line 38-41: The current catch clause "catch is AuthManagerError"
swallows any AuthManagerError variant; change the test's error handling around
handleCallbackURL to pattern-match the concrete AuthManagerError case you expect
(e.g., catch AuthManagerError.invalidCallback) and fail (or rethrow) for any
other AuthManagerError so unexpected cases like .signInSessionFailed surface at
the throw site; update the catch blocks in
AuthManagerSignInErrorVisibilityTests.swift near the handleCallbackURL
invocation to explicitly match AuthManagerError.invalidCallback and add a
XCTFail for other AuthManagerError cases.

In `@Sources/Auth/AuthManager.swift`:
- Line 137: lastSignInError is left set across different sign-in/sign-out flows
causing stale error UI; clear it whenever authentication state changes: set
lastSignInError = nil in the success paths of applySignInResult(_:) and
signInWithCredential(email:password:), after successful token seeding in
seedTokensFromCLI(...), and also clear it in signOut() and
clearSessionState(...) so every successful authenticated transition and session
clear removes the stale error (beginSignIn already clears it).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: b5e25aae-a308-4ab9-959e-9eeb6efd5713

📥 Commits

Reviewing files that changed from the base of the PR and between aea6cfc and 0cbea3d.

📒 Files selected for processing (4)
  • Resources/Localizable.xcstrings
  • Sources/Auth/AuthManager.swift
  • Sources/cmuxApp.swift
  • cmuxTests/AuthManagerSignInErrorVisibilityTests.swift

/// published `lastSignInError`. Per AGENTS.md regression test policy this
/// failing test ships in its own commit so CI proves it catches the bug.
@MainActor
final class AuthManagerSignInErrorVisibilityTests: XCTestCase {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

SwiftLint required_deinit warnings on test classes may fail CI.

Both AuthManagerSignInErrorVisibilityTests and NoopAuthTestClient are final class types without explicit deinit methods, triggering the required_deinit rule. Since these are test-only types, this has no production impact, but the warnings are flagged at the same severity level as production code and could fail the lint step.

🔧 Proposed fix
 `@MainActor`
 final class AuthManagerSignInErrorVisibilityTests: XCTestCase {
+    deinit {}
     func testInvalidCallbackURLPublishesLastSignInError() async throws {
 private final class NoopAuthTestClient: AuthClientProtocol {
+    deinit {}
     func currentUser() async throws -> CMUXAuthUser? { nil }

Also applies to: 53-53

🧰 Tools
🪛 SwiftLint (0.63.2)

[Warning] 16-16: Classes should have an explicit deinit method

(required_deinit)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmuxTests/AuthManagerSignInErrorVisibilityTests.swift` at line 16, The test
classes trigger SwiftLint's required_deinit rule; add an explicit deinit to each
test-only final class to silence the warning: locate the final classes
AuthManagerSignInErrorVisibilityTests and NoopAuthTestClient and add an empty
deinit method (deinit { }) in each class so lint passes without changing
production behavior.

Comment thread cmuxTests/AuthManagerSignInErrorVisibilityTests.swift Outdated
Comment thread Sources/Auth/AuthManager.swift
@psh4607

psh4607 commented May 6, 2026

Copy link
Copy Markdown
Contributor Author

Locally tested the visibility fix on a reload.sh --tag auth-error-vis Debug build. It works — the Account row now renders the localized error under the Sign In… button when sign-in fails. Screen recording confirms the new red text appears as expected.

Diagnostic result from this build: the surfaced error is AuthManagerError.invalidCallback ("로그인 콜백이 잘못되었습니다." in ko-KR locale), not .signInSessionFailed.

That narrows #3617's root cause significantly:

  • ASWebAuthenticationSession did receive a cmux-dev://auth-callback redirect (so it isn't canceledLogin, sandbox/TCC, or first-request network failure as I'd hypothesized in the issue body).
  • But the URL was missing valid stack_refresh / stack_access query items — AuthCallbackRouter.callbackPayload returned nil, which makes handleCallbackURL throw .invalidCallback.

Likely culprit is in web/app/handler/after-sign-in/page.tsx: the gate emits OpenNativeClient when refreshToken && accessCookie are truthy, but accessCookie can be a JSON-encoded [refreshToken, ""] (empty access token) when user.createSession() succeeds for refreshToken but getTokens() returns an empty accessToken. The Swift-side AuthCallbackRouter.decodeAccessToken then returns nil for that empty string and we land on .invalidCallback.

Will follow up with a separate PR targeting web/app/handler/after-sign-in/page.tsx to either tighten the gate (require non-empty accessToken before emitting the deep link) or fall through to the sign-in form retry instead. This visibility PR keeps its scope.

Cubic 'Cmux Swift Logging' check: 3 NSLog calls in beginSignIn now use the existing authLog helper, which is #if DEBUG-guarded.

Greptile P1 + Cubic 'Cmux Architecture Rethink' + CodeRabbit: lastSignInError is now cleared in clearSessionState (covers signOut), applySignInResult, signInWithCredential, and seedTokensFromCLI success paths so a stale error never renders alongside an authenticated row or after sign-out.

CodeRabbit test rigor: tightened catch to match AuthManagerError.invalidCallback specifically and XCTFail on any other AuthManagerError variant. Added a second regression test that primes lastSignInError = .invalidCallback and asserts signOut() clears it.
@psh4607

psh4607 commented May 6, 2026

Copy link
Copy Markdown
Contributor Author

Pushed d48988d3 addressing review feedback:

Reviewer Issue Resolution
Cubic Cmux Swift Logging 3 NSLog in beginSignIn (L216/224/234) without #if DEBUG guards Replaced with the existing authLog helper which is already #if DEBUG-guarded; same call sites, no Release-mode logs.
Greptile P1 + Cubic Cmux Architecture Rethink + CodeRabbit lastSignInError not cleared in clearSessionState / applySignInResult / signInWithCredential / seedTokensFromCLI success paths → stale red error could render after sign-out or alongside an authenticated row Added lastSignInError = nil to all four. signOut is covered transitively via clearSessionState.
CodeRabbit test rigor catch is AuthManagerError swallows any case Tightened to catch AuthManagerError.invalidCallback; an XCTFail is raised for any other AuthManagerError variant.
CodeRabbit (new) No coverage for the stale-error scenario Added testSignOutClearsStaleLastSignInError: primes lastSignInError = .invalidCallback, calls signOut(), asserts lastSignInError is nil.

Skipped:

  • CodeRabbit's deinit { } SwiftLint suggestion: repo has no .swiftlint.yml, the required_deinit rule isn't enforced, and other test files don't carry an explicit deinit. Adding it would be inconsistent without ack from maintainers.
  • Cubic Docstring Coverage warning: the project's existing style does not require docstrings on every method (most existing tests/methods don't have them). Adding 80% coverage just for this PR would be inconsistent.

Verified locally with reload.sh --tag auth-error-vis: LSP clean on all changed files, Debug build compiles, two-commit invariant preserved (1st commit's failing test still fails to compile without the fix; 2nd commit's fix + 3rd commit's review-fix both pass).

Diff size: +34/-4 across Sources/Auth/AuthManager.swift (+10/-3) and cmuxTests/AuthManagerSignInErrorVisibilityTests.swift (+24/-1).

@psh4607

psh4607 commented May 6, 2026

Copy link
Copy Markdown
Contributor Author

Closing — superseded by #3624, which lands the same visibility fix in a tighter shape: extracts AuthSignInError to a dedicated AuthManagerErrors.swift, moves AuthSettingsRow to Sources/Settings/AuthSettingsRow.swift (satisfies the CI Swift file-length budget — something my PR didn't do), adds a duplicate-sign-in guard for in-flight sessions (covers hypothesis #2 "double-fire" from #3617), and registers the v0.64.0 Settings → Sign In addition + this fix in CHANGELOG.md (also missing here).

The diagnostic data captured during local verification on this branch is still useful — particularly that the actual surfaced error on Debug builds is AuthManagerError.invalidCallback (not .signInSessionFailed), which narrows #3617's root cause to web/app/handler/after-sign-in/page.tsx emitting a deep link with a malformed [refreshToken, ""] access cookie when user.createSession()'s getTokens() returns an empty accessToken. I'll mirror that finding into a comment on #3624 and open a separate web-only PR targeting the after-sign-in handler so the actual sign-in completes once visibility lands.

@psh4607 psh4607 closed this May 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Settings → Sign In… opens auth sheet that closes immediately (no page rendered)

2 participants