Skip to content

fix(auth): env picker renders when fresh fetch returns asymmetric env keys (#797 follow-up)#805

Merged
kelsonpw merged 1 commit into
mainfrom
fix/env-picker-actually-actually-renders
May 22, 2026
Merged

fix(auth): env picker renders when fresh fetch returns asymmetric env keys (#797 follow-up)#805
kelsonpw merged 1 commit into
mainfrom
fix/env-picker-actually-actually-renders

Conversation

@kelsonpw
Copy link
Copy Markdown
Member

@kelsonpw kelsonpw commented May 16, 2026

Summary

8th fix attempt at the recurring "frozen-after-reset" env-picker bug.
The prior 7 fixes all shipped passing unit tests; the bug kept
reproducing in production. Switching strategy: drove an actual ink
render via ink-testing-library against post-deferral state, exposed
the actual gap that mocked unit tests couldn't see.

Root cause (verified from live render trace)

When pendingEnvSelection=true and setOAuthComplete brings back
fresh pendingOrgs where ONE env's app.apiKey has come back null
(provisioning lag, key rotation, role change, transient server
response — many production races land here):

  1. selectableEnvs.length === 1 in AuthScreen
  2. The auto-select-when-1-env effect (line 220) silently picks that env
  3. The credential-loading effect (line 238) takes the "selectedEnv has
    apiKey" path, calls setCredentials, clears pendingEnvSelection
  4. Router walks past Auth → DataSetup → ActivationOptions (skip) →
    Setup (skip — no setup questions yet) → Run

The user's "✓ Auth ─ ● Setup" stepper was actually Screen.Run — Run
is in the "Setup" bucket of WIZARD_STEPS. Confirmed by the
"Progress Logs Snake" tabs: those are RunScreen's tabs, not Setup's.

Why the prior 7 fixes didn't catch this

PRs #747 / #760 / #762 / #775 / #778 / #780 / #797 all reasoned about
post-deferral state with symmetric data shapes (both envs always have
keys). The mocked unit tests pinned that contract and passed. The
production failure mode involved asymmetric data between the
resolver's and authTask's two fetchAmplitudeUser fetches

specifically one env's apiKey flipping null between calls. Pure-
state assertions miss that because the React effect chain inside
AuthScreen only runs when actually mounted.

Fix

  • Gate the auto-select-when-1-env effect on !session.pendingEnvSelection.
    The resolver explicitly said the user must choose — auto-selecting
    here silently bypasses that intent.
  • Widen needsEnvPick to render the picker even with a single-env
    list when pendingEnvSelection=true. Without this, gating the
    auto-select alone would freeze the user on Auth with no actionable
    surface.

Both changes scoped to AuthScreen.tsx. No new defensive gate layer
(per the brief — 5 already exist; we found the actual gap).

Integration test

src/ui/tui/__tests__/env-picker-live-render.test.tsx boots the App
via ink-testing-library against the exact bug state (post-deferral,
fresh setOAuthComplete with asymmetric env keys), waits for effects
to fire, and asserts the router parks on Auth with the picker visible.

  • Confirmed FAILS without fix (Received: "run" vs expected "auth")
  • Confirmed PASSES with fix

The test file also pins 3 other scenarios (initial post-deferral,
same-orgs setOAuthComplete, stale-IDs scenario from #797) so future
regressions in any of those paths get caught at the live-render level
too.

Test plan

  • pnpm exec vitest run src/ui/tui/__tests__/env-picker-live-render.test.tsx — 4 pass
  • pnpm test — 4473 tests pass (no regressions)
  • pnpm tsc --noEmit — clean
  • pnpm lint — clean
  • Manual repro on a real install dir with git clean -fdx followed by re-running the wizard

🤖 Generated with Claude Code


Note

Medium Risk
Touches AuthScreen state/effect routing during authentication, which can change wizard progression and unblock/impede users if logic is wrong. Changes are localized but cover a previously flaky production edge case involving mismatched environment API key availability across fetches.

Overview
Fixes the “frozen-after-reset” env picker regression by preventing AuthScreen from auto-selecting a single environment when session.pendingEnvSelection indicates the resolver deferred to an explicit user choice, and by rendering the env picker even when only one selectable env remains under that deferral.

Adds a new ink-testing-library integration test (env-picker-live-render.test.tsx) that mounts the full TUI App, lets effects run, and asserts the UI stays on Screen.Auth with the env picker visible across multiple scenarios, including the asymmetric “one env loses apiKey on fresh fetch” case.

Reviewed by Cursor Bugbot for commit 38cfa39. Bugbot is set up for automated code reviews on this repo. Configure here.

… keys (#797 follow-up)

8 PRs deep into the recurring "frozen-after-reset" bug. Live integration
render exposed the actual gap: when `pendingEnvSelection=true` and the
fresh `setOAuthComplete` brings back pendingOrgs where one of the two
envs has lost its `app.apiKey` (provisioning lag, role change, key
rotation, transient server response — many production races land
here), `selectableEnvs.length === 1`, AuthScreen's auto-select effect
fires, the credential-loading effect takes the "selected env has
apiKey" path, and the deferral flag gets cleared. The env picker
NEVER renders — router walks past Auth into Run.

The user's "✓ Auth ─ ● Setup" stepper was actually `Screen.Run` (Run is
in the "Setup" bucket of WIZARD_STEPS). Confirmed by the
"Progress Logs Snake" tabs — those are RunScreen's tabs.

Fix:
  - Gate the auto-select-when-1-env effect on `!pendingEnvSelection`.
    The resolver explicitly said "user must choose" — auto-selecting
    silently bypasses that intent.
  - Widen `needsEnvPick` to render the picker even with a single-env
    list when `pendingEnvSelection=true`. Without this, gating the
    auto-select alone would freeze the user on Auth with no actionable
    surface — fixes the auto-resolve bug but creates a new dead-screen
    bug.

Integration test boots App via ink-testing-library, drives the post-
deferral state through `setOAuthComplete` with asymmetric env keys,
and asserts the router stays on Auth. Confirmed FAILS without the fix
(router resolves to Run) and PASSES with it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@kelsonpw kelsonpw requested a review from a team as a code owner May 16, 2026 00:34
@kelsonpw kelsonpw removed the request for review from a team May 18, 2026 18:04
@kelsonpw kelsonpw merged commit 389f6bb into main May 22, 2026
11 checks passed
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