Skip to content

Changing the local Gateway client key is not applied transactionally to the running Gateway and managed Codex client #428

Description

@NOirBRight

Reporter evidence

On Windows, changing Gateway > Local client key and pressing Apply produced this sequence:

  1. CodexHub persisted the new key, but the running Gateway continued accepting only the old key.
  2. A subsequent Restart Gateway sent POST /shutdown with the newly persisted key to the still-running old-key Gateway, which returned 401; the lifecycle then reached its force-close fallback.
  3. The replacement Gateway accepted the new key, while Codex Desktop continued sending the old managed experimental_bearer_token and received UnauthorizedLocalClient: missing or invalid local Gateway client key.
  4. Re-authenticating Codex did not repair this local credential mismatch. Restarting the whole CodexHub app republished the managed Codex overlay, after which Codex Desktop requests succeeded.

Observed timeline on 2026-08-13 (Asia/Shanghai):

  • 23:25:00 — new key persisted by CodexHub.
  • 23:34:04 — old Gateway rejected /shutdown with 401.
  • 23:34:14 — replacement Gateway started with the new key.
  • 23:35–23:37 — the external DSH client already passed local Gateway authentication, while client_id=codex-app continued receiving UnauthorizedLocalClient.
  • 23:38:04 — CodexHub app restarted.
  • 23:40 onward — Codex Desktop requests returned 200.

No credential value is included here. Hash-only comparison after recovery confirmed that CodexHub settings, the DSH credential, and the managed Codex bearer projection were identical.

Root cause

This is one transaction split across three consumers:

  • Settings Apply persists gateway_client_key, but the frontend restart predicate omits it.
  • The Python Gateway reads CODEX_PROXY_GATEWAY_CLIENT_KEY only when its process starts.
  • Ordinary Settings Apply does not republish the managed Codex config overlay containing experimental_bearer_token; the full CodexHub startup/Official-refresh publication path does.
  • Stop/restart reads the current persisted key, not the key with which the running Gateway was launched, so the old process rejects authenticated shutdown after a key rotation.

Relevant seams on the current tree:

  • frontend/src/App.tsx: gatewayRuntimeSettingsChanged, saveSettings.
  • src-tauri/src/config.rs: save_settings_with_paths, managed Codex overlay publication.
  • src-tauri/src/proxy.rs: CODEX_PROXY_GATEWAY_CLIENT_KEY, stop/restart shutdown request.
  • src-python/config_overlay.py: managed experimental_bearer_token.

Outcome

Make a local Gateway key rotation one bounded, ownership-safe transaction. A successful Apply must not leave settings, the running Gateway, or any CodexHub-managed client on different key generations.

Required behavior

  • Treat a changed gateway_client_key as a running-Gateway restart requirement.
  • Stop the old owned Gateway using the credential generation with which that process was launched, while preserving the existing ownership verification and bounded force-close contract.
  • Start exactly one replacement Gateway with the new key.
  • Republish every CodexHub-managed client configuration that carries the local Gateway key, including Codex Desktop/CLI.
  • Read back the running Gateway/client projections before reporting Apply success.
  • If any stage fails, return a truthful partial/failed state with actionable recovery; do not claim that Apply completed.
  • Keep the local Gateway key distinct from upstream Provider credentials and never place key values in logs, telemetry, UI errors, or tests.
  • Do not require an OpenAI/Codex re-login for a local Gateway key rotation.

Acceptance criteria

  • Applying a changed local key while Gateway is running retires the old process, starts one replacement, and leaves settings/Gateway/PID/listener/UI on one reconciled generation.
  • The old process receives authenticated shutdown with its launch key; a changed persisted key does not cause a shutdown 401.
  • Codex Desktop/CLI and every enabled managed external client receive and read back the new local key generation before Apply reports success.
  • The first Codex Desktop request after successful Apply authenticates without restarting CodexHub or re-logging in.
  • A client that already has the new key can authenticate immediately after the replacement Gateway starts.
  • Failure injection covers settings write, old-process shutdown, replacement start, client publication, and readback; no path reports a false success.
  • Existing lifecycle serialization, two-second user-requested shutdown, ownership verification, unrelated settings, and upstream Provider credentials remain unchanged.
  • UI/help text describes the real restart/synchronization behavior rather than claiming that key changes need no restart.

Verification

  • Focused frontend contract tests for the restart predicate and Apply result.
  • Rust lifecycle tests with distinct old/new key generations and a real authenticated shutdown fixture.
  • Managed Codex overlay plus external-client apply/readback tests.
  • Packaged Windows smoke: rotate the key with Codex Desktop and one external client configured, press Apply once, and prove both clients authenticate without restarting CodexHub.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingneeds-triageMaintainer needs to evaluate this issue

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions