Skip to content

fix(auth): surface fatal Copilot rejection on device-code sign-in; add cancel + busy feedback#114

Merged
stuffbucket merged 1 commit into
mainfrom
fix/device-code-signin-hardening
Jun 11, 2026
Merged

fix(auth): surface fatal Copilot rejection on device-code sign-in; add cancel + busy feedback#114
stuffbucket merged 1 commit into
mainfrom
fix/device-code-signin-hardening

Conversation

@stuffbucket

Copy link
Copy Markdown
Owner

What

Release-triage bundle for the device-code sign-in flow, from a three-way gap audit (state architecture / UX / slice-3 readiness) ahead of the next release.

Backend blocker

A device-code sign-in for an account with no usable Copilot (license revoked, TOS unaccepted, 401/403) routes through markAuthFatalAndSignOut — which wipes the token + on-disk record and sets the fatal-error state with its remediation URL — but runPoller then swallowed the error and unconditionally latched signed-in, papering a "signed in" UI over a wiped session and burying the reason the user needs to act on.

  • Return on CopilotAuthFatalError in the setupCopilotToken catch so the error state is preserved.
  • Re-check the abort flag before the signed-in transition, so a signOut() that fires during the Copilot-mint await can't be overwritten.
  • Two regression tests cover both paths (fatal rejection → error; sign-out-during-mint → unauthenticated).

Shell UX (same flow)

  • Cancel affordance in the device-code pending state — it was a dead-end until the code expired. Aborts the server-side poller via /sign-out (a no-op on the not-yet-minted token); no reboot.
  • startAuth now shows the ambient busy bar while the request is in flight, matching signOut/useGhAccount.
  • Dev baseUrl defaulted to :4141 while the documented fast-iteration workflow + sidecar use :4142; default to :4142, VITE_API_BASE override.

Triage note

The three audits converged that the full Phase-3 transition reducer is not required for release — #110 (guard timers) + #106 (reboot-on-signout) already closed the real hang/leak risks. This PR fixes the one remaining reachable correctness gap surgically. The Phase-3 reducer (and the cancel/sign-out semantic consolidation) stays deferred.

Testing

  • bun test — 825 pass / 0 fail (+2 new)
  • bun run check:fast — clean (typecheck root + shell, eslint, design tokens)
  • /simplify (4-agent pass) — converged on ship-as-is, no code changes

…d cancel + busy feedback

A device-code sign-in for an account with no usable Copilot (license
revoked, TOS unaccepted, 401/403) routed through markAuthFatalAndSignOut
— which wipes the token and sets the fatal-error state — but runPoller
then swallowed the error and unconditionally latched signed-in, papering
a "signed in" UI over a wiped session and burying the reason the user
needs to act on. Return on CopilotAuthFatalError to preserve the error
state, and re-check the abort flag before the signed-in transition so a
signOut() that fires during the Copilot-mint await can no longer be
overwritten. Two regression tests cover both paths.

Shell UX, same flow:
- Device-code pending state had no way out before the code expired; add a
  Cancel affordance that aborts the server-side poller via /sign-out (a
  no-op on the not-yet-minted token) and returns to the sign-in screen.
- startAuth now shows the ambient busy bar while the request is in flight,
  matching signOut/useGhAccount.
- Dev baseUrl defaulted to :4141 while the documented fast-iteration
  workflow + sidecar use :4142; default to :4142, VITE_API_BASE override.
@stuffbucket stuffbucket merged commit 0041f2e into main Jun 11, 2026
4 checks passed
@stuffbucket stuffbucket deleted the fix/device-code-signin-hardening branch June 11, 2026 20:47
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