Skip to content

fix(auth): give each login failure its own error code and stage - #442

Draft
djgould wants to merge 1 commit into
devin/init-telemetry-stagesfrom
devin/auth-login-error-codes
Draft

fix(auth): give each login failure its own error code and stage#442
djgould wants to merge 1 commit into
devin/init-telemetry-stagesfrom
devin/auth-login-error-codes

Conversation

@djgould

@djgould djgould commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Stacked on #441 — base is devin/init-telemetry-stages, since this needs the setTelemetryStage helper and the ERROR_CODE block that PR adds. Retarget to main once #441 merges.

clerk auth login reported every browser-phase failure as a single unexpected_error. All five rejection sites in the callback flow threw a plain Error, and telemetryResultForError only reads .code off CliError and ApiError — so everything else fell through to the catch-all, mixed in with every other uncaught throw in the CLI.

Failure Was Now
Browser sign-in wait expires unexpected_error auth_timeout
Provider redirects with an error unexpected_error oauth_provider_error
Callback state mismatch unexpected_error oauth_state_mismatch
Callback has no code unexpected_error oauth_no_code
Cannot bind loopback unexpected_error callback_bind_failed

Token exchange already threw ApiError and was never part of the problem.

Also adds stage markers to login — session_check → awaiting_callback → token_exchange → store → done — so an abandoned run is attributable to the step it stopped at. That matters most for awaiting_callback: prior analysis put 27–36% of login flows as never completing token exchange, with the 2-minute timeout provably binding, and until now those were indistinguishable from genuine breakage.

No disclosure change needed: error_code and stage are already covered by the README and the first-run notice, and this adds no new collected field.

Tests

8 new, all mutation-checked — each fails when the code or marker it covers is reverted. The timeout case captures the deadline callback via a setTimeout spy so it runs without waiting two minutes.

Verification

typecheck, lint, format:check clean; 2678 pass / 0 fail. E2E not run locally (needs 1Password-injected secrets).

🤖 Generated with Claude Code

Every failure in the browser-callback phase of `clerk auth login` was a plain
`Error`, and `telemetryResultForError` only reads a code off `CliError` and
`ApiError` — so a timed-out wait, an OAuth provider error, a state mismatch, a
missing authorization code, and a loopback bind failure all landed in the
warehouse as `unexpected_error`, indistinguishable from each other and from
every other uncaught throw.

Types those five sites as `CliError` with distinct codes, and instruments login
with stage markers (session_check → awaiting_callback → token_exchange → store
→ done) so an abandoned browser wait is attributable to the step it stopped at.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Aug 22, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: a3d8784

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
clerk Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

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.

1 participant