Skip to content

fix(switch): add explicit daemon restart and stale-auth recovery - #164

Merged
Loongphy merged 5 commits into
Loongphy:mainfrom
DRanger666:fix/daemon-account-switch
Oct 22, 2026
Merged

Loongphy merged 5 commits into
Loongphy:mainfrom
DRanger666:fix/daemon-account-switch

Conversation

@DRanger666

@DRanger666 DRanger666 commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

With Codex 0.160.0, switch can successfully select account A in auth.json and the registry while the shared app-server daemon continues using account B. Newly opened clients can attach to that same daemon. This was confirmed through the supported account/read request with refreshToken: false.

This adds switch <query> --restart-daemon to persist the selection and then restart a responsive daemon through Codex's supported lifecycle command. Explicit requests also work when the selected files are unchanged, so users can repair stale daemon authentication or retry a failed restart.

Why another PR alongside #163?

codejunkienick's #163 correctly identifies the daemon issue and proposes the same supported restart mechanism. This offers a different user contract and hardens the lifecycle operation:

Behavior #163 This PR
Restart policy On by default; persisted opt-out setting Explicit --restart-daemon; no registry/schema change
Detection Endpoint exists Endpoint check plus bounded daemon version response
Restart waiting Unbounded child wait Fixed 120-second budget, including exit after output EOF
Requested restart failure Switch still succeeds; JSON mode is silent Exit 1; selection JSON remains on stdout, lifecycle diagnostics on stderr

These differences warrant review as an alternative implementation. They can also inform improvements to #163; this PR does not assume how maintainers will reconcile the two.

Behavior and scope

  • Query, one-shot picker, and previous-account routes share one activation/persistence boundary. No restart runs before those operations succeed.
  • Without the option, responsive daemons produce a stderr hint; no restart is performed. --live --restart-daemon is rejected; live switching retains its existing behavior.
  • Confirmed absence skips restart successfully. An indeterminate probe prevents restart and fails an explicit request. Detection is best effort: a daemon disappearing after the probe may be started by Codex's restart command.
  • Failure preserves the selected files. Timeout terminates and reaps only the spawned helper, reports the outcome as unconfirmed, and supplies manual status/recovery commands with the selected home and executable. There is no automatic retry, credential rollback, or daemon-process kill.
  • Help and command documentation explain interruptions, unchanged-file recovery, timeout uncertainty, and successful selection JSON accompanying a later nonzero exit.

Validation

  • Isolated Linux suite: 448 passed, 5 skipped; baseline: 435 passed, 5 skipped.
  • Required zig build run -- list, formatting, and whitespace checks passed.
  • Native ReleaseSafe build and Windows/macOS ReleaseSafe cross-builds passed. Cross-builds do not establish runtime behavior on those platforms.
  • Upstream native test CI passed on Linux, macOS, and Windows; all six preview-package builds passed. All 12 checks on commit 0836af1 are green.
  • Tests cover selected CODEX_HOME, responsive/absent/indeterminate probes, unchanged selection, failed restart and retry, JSON stdout plus stderr/exit 1, picker/previous routes, relative/empty PATH entries and skipped non-executable Unix candidates, and timeout cleanup with both open and closed output pipes.

Real Linux acceptance with Codex 0.160.0 passed: disk already selected A while the daemon reported B; explicit unchanged-selection restart restored A. Selecting A without the option kept the same daemon PID. Subsequent A→B→A switches changed daemon PIDs and returned the expected identities through supported account/read requests with refreshToken: false. Configuration/history checks passed, and the candidate was selected for local use. The existing active chat briefly displayed “Reconnecting to server…”, reconnected using the newly selected account, and continued in the same session without manual exit/resume.

Related: #162, #163.

Probe a responding daemon through the supported version command and restart only on an explicit request. Bound output collection and process exit, including helpers that close their pipes before exiting; terminate and reap only our child on timeout.

Use the selected CODEX_HOME and prefer its managed executable. Keep absent and indeterminate states distinct, accepting the supported lifecycle detection/restart race.

Motivated by codejunkienick’s PR Loongphy#163 (Loongphy#163); implements its daemon coordination idea with an explicit contract and independent code. No persistent configuration or registry schema changes.
Add --restart-daemon to one-shot query, picker, previous-account and JSON paths at their shared activation boundary. Persist account and registry state before daemon coordination; explicit requests include unchanged-file recovery and retry.

Preserve the existing JSON stdout document and put lifecycle diagnostics and quoted home-specific recovery commands on stderr. Requested restart failures return the existing operational exit code 1. Reject the option with --live and retain other activation behavior.

Cover selected-home propagation, persistence ordering, unchanged selection, previous and multi-match routes, failed selection, absent and indeterminate daemons, JSON failures and retry. Native suite: 446 passed, 5 skipped; required isolated list smoke and Windows/macOS cross-builds passed.

Related: Loongphy#162 and codejunkienick’s Loongphy#163. This opts into disruptive restarts without adding persistent config or changing the registry schema.
Use the cwd-relative canonicalization API for PATH candidates, which can be relative. The absolute-only API asserts on a valid relative PATH entry when managed Codex layouts are unavailable.

Add an isolated CLI regression that removes the managed executable, finds the lifecycle helper through a relative PATH directory, and verifies restart and selected-home propagation. Linux suite: 447 passed, 5 skipped.
DRanger666 added a commit to DRanger666/codex-auth that referenced this pull request Oct 2, 2026
Merge fix/daemon-account-switch without rewriting published history. Update delivery notes to the final 5baaa35 candidate, 447 passing tests, and upstream PR Loongphy#164. Keep local delivery documentation and publishing guards out of the upstream-facing branch.
@pkg-pr-new

pkg-pr-new Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

@loongphy/codex-auth-darwin-arm64

npx https://pkg.pr.new/@loongphy/codex-auth-darwin-arm64@0836af1

@loongphy/codex-auth-darwin-x64

npx https://pkg.pr.new/@loongphy/codex-auth-darwin-x64@0836af1

@loongphy/codex-auth-linux-arm64

npx https://pkg.pr.new/@loongphy/codex-auth-linux-arm64@0836af1

@loongphy/codex-auth-linux-x64

npx https://pkg.pr.new/@loongphy/codex-auth-linux-x64@0836af1

@loongphy/codex-auth-win32-arm64

npx https://pkg.pr.new/@loongphy/codex-auth-win32-arm64@0836af1

@loongphy/codex-auth-win32-x64

npx https://pkg.pr.new/@loongphy/codex-auth-win32-x64@0836af1

@loongphy/codex-auth

npx https://pkg.pr.new/@loongphy/codex-auth@0836af1

commit: 0836af1

Check executable access for Unix managed and PATH candidates so a non-executable regular file cannot mask a later usable Codex binary. Retain the regular native executable check on Windows.

Extend the relative-PATH regression to ./fallback and empty PATH entries, and add an isolated CLI test with a non-executable decoy before the valid helper. No new resolver abstraction. Linux suite: 448 passed, 5 skipped; required isolated list smoke passed.

@Loongphy Loongphy 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.

Thanks — this is a real problem and I like the contract here: explicit opt-in, no schema changes, exit 1 with JSON preserved on stdout and diagnostics on stderr. I verified the mechanism against upstream OpenAI codex (app-server daemon {version,restart} subcommands, the $CODEX_HOME/app-server-control/app-server-control.sock path, and the LifecycleOutput JSON shape all match), and the full test suite passes on this branch (zig 0.16).

One requested change: make the mechanism two-tier, because our Codex fork already ships a purpose-built, non-disruptive command for exactly this flow.

codex app-server daemon reload-auth exists in our fork (tagged [codext]; it landed there on 2026-10-11, after this PR was opened, so it's not in your base). Its doc comment states the intended use verbatim:

External account switchers (codex-auth) run this after replacing auth.json so daemon-hosted sessions pick up the new account without restarting the daemon.

reload-auth makes the running daemon re-read auth.json in place: sessions stay alive, no client reconnection, and the daemon skips it while a turn is running (SkippedBusy). It returns Unsupported when pointed at a non-codext daemon, which gives you a clean capability probe for the fallback.

Suggested behavior — keep the --restart-daemon flag name and everything around it, change what happens underneath:

  1. Keep your probe as-is (socket stat → daemon version; .not_running short-circuits before any spawn).
  2. If the daemon is running, first attempt codex app-server daemon reload-auth with its own short timeout (suggest ~10s, not the 120s restart budget), so a hung daemon fails fast.
    • Reload succeeds → done: report success without restarting (add an Outcome variant, e.g. .reloaded, and a matching diagnostic line). This is the common case on codex.
    • Unsupported (official daemon), spawn failure, or timeout → fall back to codex app-server daemon restart exactly as implemented today.
  3. SkippedBusy (daemon deferred because a turn is running): treat as deferred, exit 0, and do not restart — restarting would kill the very turn that made the daemon busy. The diagnostic should tell the user the switch will be picked up once the turn finishes (retry switch --restart-daemon later).
  4. Exit 1 only when both reload and restart failed/were impossible; stdout JSON semantics unchanged.

Why this shape:

  • Official codex is unaffected: on an upstream daemon the first tier returns Unsupported and you fall through to your existing restart path — so this PR remains the correct (and only) mechanism there.
  • On codex it turns a disruptive operation into a seamless one: no killed turns, no client reconnection, for the single most common case (switch account, keep working).

Latency: no added cost on a plain switch (the two-tier only applies when --restart-daemon is passed). With the flag, the added cost is one bounded UDS subprocess (~100 ms typical for CLI startup + roundtrip) — negligible next to a restart, which takes seconds plus client reconnection on the codex side, and which the reload tier avoids entirely when supported.

Happy to review the follow-up push. The maintainer-side implementation is also prototyped in #172 (draft, stacked on this branch: two-tier ensure/tryReload + reload-auth fake scenarios) — either land the same shape here or merge #172 after this PR; both paths work.

Two-tier --restart-daemon coordination, per the maintainer review:

- ensure() probes the daemon, then attempts 'app-server daemon
  reload-auth' (codex daemons) with a 10s budget before falling back to
  the full restart (upstream daemons without reload support)
- reload outcomes: .reloaded (credentials applied in place, no restart)
  and .deferred (daemon busy with a running turn — deferred, exit 0,
  rerun later); reload unavailability or failure falls through to the
  existing restart path
- diagnostics for both new outcomes; docs updated
- fake lifecycle script gains reload-auth variants; client + integration
  expectations updated for the version → reload-auth → restart sequence
[skip ci]

@Loongphy Loongphy 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.

Verification passed on the updated head (afded66) — switching this review to approved.

Autoreview (codex, independent pass): overall: patch is correct (0.98) — no accepted/actionable findings in scope; "adds a daemon auth reload attempt with restart fallback and a busy-turn deferral. No P0 defect ... is evident."

Local validation:

  • zig build -Doptimize=ReleaseFast clean; full zig build test exit 0 (including the three new reload scenarios and the updated integration sequences).
  • E2E with a fake codex daemon: switch beta --restart-daemon now runs version → reload-auth, reports "the running daemon reloaded the new credentials without a restart", and does not restart (no session interruption); the reload-unsupported path still falls back to restart, and the busy path defers with exit 0.

Good work on the quick turnaround — merging when you're ready.

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.

2 participants