Skip to content

fix(startup): use selected core for auth preflight - #2640

Merged
john-the-dev merged 8 commits into
mainfrom
fix/codex-startup-auth-preflight
Aug 5, 2026
Merged

john-the-dev merged 8 commits into
mainfrom
fix/codex-startup-auth-preflight

Conversation

@john-the-dev

@john-the-dev john-the-dev commented Aug 4, 2026 •

Copy link
Copy Markdown
Collaborator

What changed, and why?

startup.sh selected core.runtime only after it had copied Claude credentials and run the Claude-only boot gate. A host configured for Codex could therefore abort with a Claude /login remedy even though codex login status passed.

This change resolves the runtime before credential handling, keeps Claude credential carry Claude-only, and dispatches the early auth gate by runtime. Codex now exports the configured CODEX_HOME and checks codex login status before any service bootstrap. Claude keeps the existing rich preflight unchanged.

Review first

  • src/startup-runtime.sh: early runtime selection and selected-core preflight
  • src/startup.sh: activated ordering and Claude-only credential carry
  • tests/startup-core-auth-routing.test.sh: runtime isolation, fail-closed, escape-hatch, and activated-wiring coverage

User behavior proved

A Codex-configured startup no longer reads/copies Claude login state or asks the owner to authenticate Claude. It fails with a Codex-specific remedy only when Codex itself is unavailable or logged out.

Documentation consistency

Updated the canonical runtime dependency and Codex setup docs, their docs/catalog.json verification dates, and the README setup guidance.

Before / after evidence

Parent 0604188b, real isolated startup with SUTANDO_CORE_RUNTIME=codex:

PARENT=0604188b
PARENT_STARTUP_RC=2
✗ auth-preflight-gate: CLI login required for /private/tmp/sutando-codex-auth-base.olDy8E/workspace/.claude-sutando — ABORTING startup
Remedy: needs GUI /login on Ruis-Mac-mini: open Terminal there and run `CLAUDE_CONFIG_DIR=/private/tmp/sutando-codex-auth-base.olDy8E/workspace/.claude-sutando claude` ...
PARENT_TEST_SESSIONS=none

HEAD 91eb3051, same real startup path with an isolated workspace/socket/session (GUI commands suppressed so the test did not launch Sutando.app):

STARTUP_RC=0
codex-auth-preflight: OK — /Users/ruiwang/.codex can boot authenticated
SESSION_RUNTIME=codex
NATIVE_APP_COUNT=0

Real post-startup task/result round trip through the isolated Codex core and notifier:

ROUND_TRIP_RESULT=LIVE_CODEX_STARTUP_OK

The activated regression test run against the parent source fails (3 passed, 12 failed); the same test at HEAD passes:

Results: 15 passed, 0 failed

Tests

bash tests/startup-core-auth-routing.test.sh        # 15 passed, 0 failed
python3 tests/auth-preflight-gate.test.py           # 7 passed
python3 tests/core-runtime-config.test.py            # 4 passed
bash tests/startup-env-token-persist.test.sh         # 7 passed, 0 failed
bash tests/startup-voice-managed-gate.test.sh        # PASS
bash tests/startup-voice-optional.test.sh             # PASS
npm run typecheck                                    # PASS
bash -n src/startup.sh src/startup-runtime.sh tests/startup-core-auth-routing.test.sh
bash scripts/review-checks.sh --diff <commit.diff>   # hardcoded-paths clean

Additional startup regression scripts passed: re-migrate loop, self-log temp path, shell setup wiring, stand-identity self-heal, and voice optionality.

python3 scripts/gen-src-map.py --check remains red on both parent and HEAD because the checked-in map says 193 modules while current main generates 194; this PR does not add a src/ module and does not bundle that pre-existing documentation drift.

Edge cases

  • .env runtime override participates in early selection without leaking unrelated .env values into the parent shell.
  • Logged-out Codex fails before service bootstrap and names codex login.
  • SUTANDO_SKIP_AUTH_PREFLIGHT=1 skips the early check without invoking a CLI.
  • Claude still uses the existing gate and credential carry.
  • Unsupported runtimes fail explicitly.

Risk / rollback

No migration or permission change. Roll back this commit to restore the old ordering. The shared one-run recovery escape hatch remains available.

Stacking / known gaps

Not stacked. This is intentionally one concern. The separate headless-startup issue (startup currently tries to launch the macOS app) is not bundled here and should be handled independently.

@john-the-dev
john-the-dev force-pushed the fix/codex-startup-auth-preflight branch from f59acd4 to 91eb305 Compare August 4, 2026 16:00
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Coverage Gate

✅ No Python changes — nothing to gate (bar: 95% on changed lines).

@bassilkhilo-ag2

Copy link
Copy Markdown
Collaborator

Cold review, cloned and verified rather than just reading:

  • bash tests/startup-core-auth-routing.test.sh — 15/15 pass, including the two claims that matter most: "Codex does not call Claude auth gate" and "runtime selection precedes credential carry" (the actual bug being fixed).
  • bash -n on both src/startup.sh and the new src/startup-runtime.sh — clean syntax.
  • Before/after evidence in the body is a real isolated startup run (parent vs head), not a synthetic example — good.

One real gap, not inherited noise: this PR adds src/startup-runtime.sh (confirmed new via gh pr diff --name-only) but never regenerates docs/src-map.md — python3 scripts/gen-src-map.py --check fails on this branch specifically because of that, distinct from the main-is-red issue #2634/#2635 already fixed (which is also still inherited here since this branch predates them, but that's not this PR's fault). This one is: python3 scripts/gen-src-map.py and commit the result.

Nothing else found. Small, mechanical fix once the src-map is regenerated - not blocking on substance, just flagging the gate that's currently red for a PR-caused reason.

@sonichi sonichi left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Reviewed at head 91eb3051, on top of 80b7d492.

Premise confirmed on main before endorsing anything. At 80b7d492 the ordering bug is exactly as described:

  • src/startup.sh:207-261 — Claude credential carry (.credentials.json, .claude.json seeds, env-token persist)
  • src/startup.sh:292 — auth-preflight-gate.sh … || exit $?, the Claude-only gate that aborts startup
  • src/startup.sh:531 — where core_runtime was actually resolved, ~220 lines later

So a Codex host does reach a Claude /login abort before anything consults core.runtime. Real bug.

Control / treatment. I ran your new suite against main's src/ (test file only, checked out over the old source) and then at your head:

main's src/ + new test : EXIT=1   3 passed, 12 failed
                         FAIL runtime selection precedes credential carry: select= carry=
                         FAIL selected auth gate precedes service bootstrap: gate= init=479
PR head 91eb3051       : EXIT=0   15 passed, 0 failed

It genuinely reproduces the failure rather than asserting the new arrangement. All 8 existing tests/startup-*.test.sh suites also pass at head. scripts/review-checks.sh --diff PASS; no hardcoded host paths in the 239 added lines; no absolute /usr/bin/ tool invocations (REVIEW.md lesson 7) — codex is called by bare name.

Three things I checked because they looked wrong and aren't — recording them so the next reviewer doesn't re-derive:

  1. The claude arm skips the gate when _claude_config_dir is empty, which reads like a new fail-open. It isn't: main:292 is already wrapped in if [ -n "${CLAUDE_CONFIG_DIR:-}" ]. Byte-for-byte the same reachability.
  2. export CLAUDE_CONFIG_DIR="$_ccd" sits outside the new claude_auth_carry_enabled block. That's correct and load-bearing — Codex hosts still need CLAUDE_CONFIG_DIR for channels/*/access.json, so gating the export would have broken every bridge on a Codex core.
  3. The new case hard-codes claude|codex. I checked _SUPPORTED_CORE_RUNTIMES in src/sutando_config.py — it is exactly {claude, codex}, so the domain is complete and *) is defensive rather than a live gap.

One non-blocking observation, on blast radius rather than correctness.

resolve_startup_core_runtime() ends in || true, and the call site does [ -n "$core_runtime" ] || core_runtime="claude". resolve_core_runtime() raises on an unsupported value, so an operator who writes core.runtime: "gemini" gets an empty result, silently falls back to claude, and startup proceeds as a Claude core ignoring their stated intent.

That is identical to main's old line 531, so it is not a regression from this PR and I would not hold the merge for it. What changed is the consequence: on main that fallback only chose which binary command -v probed; here it decides whether Claude credential carry runs and which auth gate gates the boot. Same door, larger room behind it. Worth a follow-up that distinguishes "resolver failed" from "resolver returned nothing", rather than folding both into the Claude default.

Nit, non-blocking: the if claude_auth_carry_enabled …; then wrapper leaves the ~70 enclosed lines at their original indentation. Re-indenting would have made the diff much harder to audit, so I think you made the right call — noting it only so it isn't read as an accident.

No blockers from me. Comment-only review — I have no approval authority here; merges are Chi's call.

@bassilkhilo-ag2 bassilkhilo-ag2 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Verified independently on a fresh clone of fix/codex-startup-auth-preflight (not the frozen local app-bundled checkout):

$ bash tests/startup-core-auth-routing.test.sh
  ok  .env runtime override is visible to early selection
  ok  early selection does not leak unrelated .env values
  ok  authenticated Codex preflight passes
  ok  Codex status uses configured CODEX_HOME
  ok  Codex does not call Claude auth gate
  ok  logged-out Codex fails closed
  ok  logged-out Codex names its own remedy
  ok  Codex skip hatch passes
  ok  Codex skip hatch executes no CLI
  ok  Claude preflight still passes through
  ok  Claude preflight still receives CLAUDE_CONFIG_DIR
  ok  Codex disables Claude credential carry
  ok  Claude keeps credential carry
  ok  runtime selection precedes credential carry
  ok  selected auth gate precedes service bootstrap
Results: 15 passed, 0 failed

Mutation check on the same checkout — disabled the codex login status branch (if false; then) in preflight_selected_core_auth, reran: 3 of the 15 assertions correctly flip to FAIL (authenticated Codex preflight passes, logged-out Codex fails closed, and its remedy-message check). Confirms the suite is exercising the real gate, not passing vacuously. Restored the file after.

Also traced the "activated wiring" claim past the helper itself: tests/startup-core-auth-routing.test.sh greps the real src/startup.sh for core_runtime="$(resolve_startup_core_runtime)", if claude_auth_carry_enabled "$core_runtime", and preflight_selected_core_auth "$core_runtime" and asserts their line-number order relative to the Claude credential-carry block and init.sh --auto. That's checking the call site is reached in the right place, not just that the extracted functions are individually correct.

Diff is internally consistent: the old direct bash "$REPO/src/auth-preflight-gate.sh" "$CLAUDE_CONFIG_DIR" call and the old duplicate core_runtime=$(...) resolution near the L527 CLI-presence check are both removed in favor of the single early resolution + dispatch — no leftover duplicate logic. Docs (README.md, docs/codex-core.md, docs/runtime-dependencies.md, docs/catalog.json last_verified bump) match the new behavior.

The two failing CI checks (refuse docs/src-map.md stale vs src/, tsc + tests (clean install)) are the known inherited main-red src-map count-line staleness affecting every open PR right now (tracked by #2635/#2638) — this PR adds no new src/ module and isn't the cause; confirmed via gh pr diff --name-only (only startup-runtime.sh, startup.sh modified, no new src files) and the failing job's own log showing docs/src-map.md is stale as the sole failure.

Approving.

@john-the-dev

Copy link
Copy Markdown
Collaborator Author

Addressed the source-map comment and refreshed onto current main at head ee196118.

  • regenerated docs/src-map.md after adding src/startup-runtime.sh
  • resolved the generated-map merge conflict by rerunning the canonical generator
  • python3 scripts/gen-src-map.py --check — pass
  • bash tests/startup-core-auth-routing.test.sh — 15/15 pass
  • bash -n src/startup.sh src/startup-runtime.sh — pass
  • git diff --check origin/main...HEAD — clean

The branch is current; hosted checks and fresh exact-head approvals are now required.

@john-the-dev

Copy link
Copy Markdown
Collaborator Author

Refreshed this release-gate branch onto current main after #2657, #2654, #2652, and #2656 landed. New exact head: 206724a5deab2a7dcc8e4c64d28b815afd872ebd.

Independent post-refresh verification from a detached worktree:

bash tests/startup-core-auth-routing.test.sh  -> 15 passed, 0 failed
for f in tests/startup-*.test.sh; do bash "$f"; done -> every startup suite passed
git diff --check origin/main...HEAD          -> pass
git diff origin/main...HEAD | bash scripts/review-checks.sh
                                                review-checks: PASS (hardcoded-paths clean)

The focused Codex route still proves early runtime selection, authenticated/logged-out fail-closed behavior, Claude-gate exclusion, configured CODEX_HOME, credential-carry separation, and pre-bootstrap ordering. The PR-owned fix remains intact across the merge refresh.

Hosted checks are rerunning. Prior approvals predate this merge-only head, so please review exact 206724a5 after CI completes. No merge performed.

@qingyun-wu qingyun-wu left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed exact head 206724a after its current-main refresh.

The full current diff and startup/auth paths are sound. Runtime selection now precedes Claude credential carry, Codex exports the same configured CODEX_HOME as its launcher and checks codex login status, Claude retains its existing gate, and the shared skip hatch does not execute the Codex CLI. The focused 15-case routing suite, shell syntax, diff hygiene, and REVIEW.md hardcoded-path checks pass; completed hosted gates and CLA are green.

Worst case is a Codex host being blocked by Claude credentials or authenticating against the wrong home; the selected-runtime ordering, no-Claude-call, configured-home, logged-out, and skip controls cover those paths. Code approved. Clean-install is still running and merge state remains blocked, so this is not yet ready to merge under the complete repository gate.

@john-the-dev
john-the-dev enabled auto-merge (squash) August 5, 2026 13:30

@qingyun-wu qingyun-wu left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed exact head fc9bd1e17462aab849ec6643b9d5cb957f2b539d.

No blocking findings. This is a merge-only refresh of the previously approved startup auth-preflight topic, now incorporating #2674. I compared the complete effective topic patch before and after both refreshes; the normalized file/patch fingerprint is identical. The prior focused 15-case startup/auth suite and mutation check therefore still exercise the exact production changes. The worst-case user impact remains a startup refusal for an unauthenticated selected core, which is intentional and includes the core-specific remedy plus an explicit skip hatch.

APPROVED for code. Merge-ready only after hosted checks are green and the repository's second current-head maintainer approval requirement is satisfied.

Reviewed by Qingyun's Personal Codex.

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

@cla-assistant check

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.

4 participants