Skip to content

fix(auth): structural gate covers no-pre-selection case (env-picker hang follow-up)#780

Merged
kelsonpw merged 1 commit into
mainfrom
fix/env-picker-hang-after-checkpoint-invalidate
May 15, 2026
Merged

fix(auth): structural gate covers no-pre-selection case (env-picker hang follow-up)#780
kelsonpw merged 1 commit into
mainfrom
fix/env-picker-hang-after-checkpoint-invalidate

Conversation

@kelsonpw
Copy link
Copy Markdown
Member

@kelsonpw kelsonpw commented May 14, 2026

Summary

When git reset --hard wipes <installDir>/.amplitude/ and PR #778's loadCheckpoint invalidates the per-user checkpoint, the session reaches Auth with selectedOrgId === null AND selectedProjectId === nullresolveCredentials does NOT set those in the multi-env defer branch.

PR #775's structural gate (needsEnvPickStillRequired) short-circuited to false in that case, leaving only pendingEnvSelection as a gate. The recurring "stuck on Setup with no env-picker" bug class then reproduces if anything clobbers that flag — exactly the symptom users keep reporting (✓ Welcome ─ ✓ Auth ─ ● Setup ←, no env picker rendered, TUI stuck).

This commit extends the structural gate with a fallback path that mirrors resolveCredentials' own "first project of first org" heuristic (credential-resolution.ts ~line 483). When no specific selection has been made, the gate consults pendingOrgs[0].projects[0] — the exact (org, project) tuple resolveCredentials walked when it issued the deferral. Once the user picks an org/project via AuthScreen, the existing specific-IDs guard takes over.

Why this is the right layer

Prior PRs in this bug class:

#778 (the most recent fix) is doing its job — invalidating the stale checkpoint. But it surfaces a case PR #775's gate didn't cover: when no IDs are pre-selected. This commit plugs that gap precisely.

Files changed

  • src/ui/tui/flows.tsneedsEnvPickStillRequired adds the IDs-null fallback to pendingOrgs[0].projects[0]
  • src/ui/tui/__tests__/flow-invariants.test.ts — adds smoking-gun regression test (fails without the fix), counter-test (manual-API-key carve-out), wider restart-after-reset router-resolve test
  • src/ui/tui/screens/__tests__/AuthScreen.snap.test.tsx — pins env picker rendering when pendingEnvSelection: true

Test plan

  • pnpm tsc --noEmit clean
  • pnpm lint clean
  • pnpm test — all 4258 tests pass (after one flaky unrelated re-run on agent-interface MCP auth refresh test)
  • New regression test structural gate alone holds Auth in restart-after-reset fails without the flows.ts change (verified via stash + re-test)
  • wizard-abort.ts untouched
  • No changes to other env-picker logic — minimal-surface fix

🤖 Generated with Claude Code


Note

Medium Risk
Changes the Auth completion gating logic in the TUI wizard, which can affect routing between major setup steps; however it’s a targeted fallback with added regression tests covering the affected restart scenarios.

Overview
Prevents the wizard from advancing past Auth (into Setup/post-auth screens) when multi-environment selection is still required but selectedOrgId/selectedProjectId are null (e.g. after checkpoint invalidation on restart).

Updates needsEnvPickStillRequired to fall back to the same pendingOrgs[0].projects[0] target used by credential resolution, and adds regression/counter tests plus an AuthScreen snapshot to pin env-picker rendering and correct router parking behavior.

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

… no IDs selected

When `git reset --hard` wipes `<installDir>/.amplitude/` and PR #778's
`loadCheckpoint` invalidates the per-user checkpoint, the session
reaches Auth with `selectedOrgId === null` AND `selectedProjectId ===
null` — `resolveCredentials` does NOT set those in the multi-env defer
branch. PR #775's structural gate (`needsEnvPickStillRequired`)
short-circuited to `false` in that case, leaving only
`pendingEnvSelection` as a gate. The recurring "stuck on Setup with no
env-picker" bug class then reproduces if anything clobbers that flag —
exactly the symptom users keep reporting (`✓ Welcome ─ ✓ Auth ─ ●
Setup ←`, no env picker rendered, TUI stuck).

This commit extends the structural gate with a fallback path that
mirrors `resolveCredentials`' own "first project of first org"
heuristic (credential-resolution.ts ~line 483). When no specific
selection has been made, the gate consults `pendingOrgs[0]
.projects[0]` — the exact (org, project) tuple `resolveCredentials`
walked when it issued the deferral. Once the user picks an org/project
via AuthScreen, the existing specific-IDs guard takes over.

Guard reordering: `pendingOrgs === null` short-circuit stays first
(unchanged manual-API-key carve-out). `selectedEnvName !== null`
short-circuit also stays — picking an env (real or auto-select) is
still the canonical "we're done with this gate" signal regardless of
which (org, project) tuple we resolved through.

Regression coverage in `flow-invariants.test.ts`:
  - `structural gate alone holds Auth in restart-after-reset`: pins
    the smoking-gun scenario (credentials + names set, IDs null,
    pendingEnvSelection clobbered). Fails without the fix.
  - `structural gate does not block manual-API-key path`: counter-test
    keeping `pendingOrgs === null` carve-out intact.
  - `parks on Auth after checkpoint invalidation + multi-env defer`:
    wider integration test through the full bin.ts mutation sequence.
  - Plus the AuthScreen snapshot test confirming the env picker
    renders when `pendingEnvSelection: true`.

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 14, 2026 22:22
@kelsonpw kelsonpw merged commit e26e504 into main May 15, 2026
11 checks passed
kelsonpw added a commit that referenced this pull request May 15, 2026
…stale selectedOrgId/ProjectId (#797)

Prior PRs #747 / #760 / #762 / #775 / #778 / #780 each tried to fix the
recurring "stuck on Setup with no env-picker" hang after `git reset --hard`
on a previously-instrumented project. Each landed a gate that worked at
the unit-test level, but the live repro kept reproducing.

This pins the actual gap PR #780 left behind. PR #780's structural fallback
(`needsEnvPickStillRequired` → `pendingOrgs[0].projects[0]`) only fires
when BOTH `selectedOrgId` AND `selectedProjectId` are null. In the real
restart-after-reset sequence:

  1. `resolveCredentials` populates `pendingOrgs` (first fetch) and defers
     on multi-env.
  2. `applyEnvSelectionDeferral` sets `pendingEnvSelection=true`.
  3. AuthScreen mounts and its single-org/single-project auto-resolve
     effect writes `selectedOrgId='org-1'`, `selectedProjectId='proj-1'`
     from that first snapshot.
  4. `authTask` runs OAuth, `fetchAmplitudeUser` fetches a SECOND time,
     and `setOAuthComplete` REPLACES `pendingOrgs` with the fresh data.
     If the two snapshots' IDs don't match (re-fetch race, ordering,
     account changes, stale in-memory IDs), the existing
     `pendingOrgs.find(o => o.id === selectedOrgId)` lookup returns
     `undefined`.
  5. The structural gate short-circuits to `false`. If anything clobbers
     `pendingEnvSelection=false` (the recurring failure-mode the 6 prior
     PRs chased — every fix relied on the flag staying true), Auth.isComplete
     returns true and the router walks past Auth into the Setup-bucket
     screens. User sees `✓ Auth ─ ● Setup ←` with no env picker on
     screen.

Fix: when stale `selectedOrgId/ProjectId` don't resolve a project in
`pendingOrgs`, fall through to the same `pendingOrgs[0].projects[0]`
heuristic instead of bailing to `false`. The structural gate is now
load-bearing across all three states:

  (a) no IDs picked yet (PR #780)
  (b) IDs picked and valid in pendingOrgs (existing happy path)
  (c) IDs picked but stale relative to fresh pendingOrgs (this PR)

Regression test: src/ui/tui/__tests__/env-picker-restart-after-reset-hang.test.ts
drives the complete bin.ts startup sequence — buildSession → store.session →
resolveCredentials multi-env defer → applyEnvSelectionDeferral →
concludeIntro → setOAuthComplete → AuthScreen's setOrgAndProject — and
asserts the router parks on Auth at every step. The new "stale IDs"
test fails without the fix (router resolves to data-setup) and passes
with it.

Pre-existing test in env-picker-repro.test.ts that asserted the OPPOSITE
behavior ("does not block when the resolved project is missing from
pendingOrgs") was inverted to match the new (correct) behavior — that
test was inadvertently encoding the bug as expected behavior.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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