Skip to content

codex login removes the existing auth.json before the flow can succeed, so a failed login logs the user out #46430

Open

Description

@trinity0506

[Bug] codex login removes the existing auth.json before the flow can succeed, so a failed login logs the user out

Component: Codex CLI (codex login, codex login --device-auth)
Version: tested on codex-cli 0.153.4 (linux-x86_64, musl standalone build)

Summary

codex login appears to delete the existing credentials at the start of the flow
rather than writing new ones atomically on success. If the flow fails at any later step
(browser not reachable, token exchange transport error, expired device code, wrong account
in the browser, …), the user is left logged out even though they were logged in before.

This turns a recoverable, transient failure into a destructive one, and it is especially
painful on headless/remote hosts where the only way to fix it is to re-run a login flow
that just failed.

Steps to reproduce

  1. Have a working login: codex login status → Logged in using ChatGPT.

  2. Start a new login that will fail late, for example while the token endpoint is
    temporarily unreachable:

    codex login --device-auth
    # complete the browser step
    # -> ERROR: Error logging in with device code: device code exchange failed:
    #    error sending request for url (https://auth.openai.com/oauth/token)
  3. Check the state:

    ls ~/.codex/auth.json      # No such file or directory
    codex login status         # Not logged in

Observed twice in a live environment (a remote Linux host): the previously valid
auth.json was gone after a failed login attempt.

Expected

Either:

  • the existing credentials are left untouched until the new ones are successfully
    obtained (write to a temp file, then atomically rename), or
  • the old credentials are backed up and restored automatically when the flow fails,
    or at minimum
  • the CLI warns that continuing will remove the current login.

Actual

The previous credentials are removed before the flow completes; a failed login means the
user must successfully complete a login (possibly under the same broken network
conditions) before Codex works again.

Impact

  • On remote/headless machines, a single transient network error can take a working Codex
    installation offline until an operator can re-authenticate.
  • Makes it risky to attempt a login (e.g. to switch accounts) while anything about the
    network path is uncertain.

Suggestion

  • Write credentials atomically; only replace the previous file on success.
  • On failure, restore the previous credentials and say so in the error output.
  • Consider printing the target CODEX_HOME and whether an existing login will be
    replaced, since CODEX_HOME may point at a non-default directory.

Environment

codex-cli 0.153.4
Linux x86_64 (headless server), CODEX_HOME=/<user>/.codex (non-default path)
Reached OpenAI through a local HTTP proxy (HTTP_PROXY/HTTPS_PROXY set for the process)

Related issues

Minimal fix sketch

// pseudo-code: make credential replacement atomic and non-destructive on failure
let tmp = auth_path.with_extension("json.tmp");
write(tmp, &new_credentials)?;          // only after the flow has fully succeeded
fs::rename(tmp, auth_path)?;            // atomic replace
// on any failure path: leave the previous auth.json untouched

Activity

  1. added
    bugSomething isn't working
    CLIIssues related to the Codex CLI
    authIssues related to authentication and accounts
    on Sep 18, 2026
  2. 84dnnvbdvp-debug commented on Sep 18, 2026

    @84dnnvbdvp-debug

    Current main (7498521d288b9b3b96ffba4eedf089d8d6e06a84) makes this more destructive than an auth.json atomic-write bug, so the minimal fix sketch should move one boundary earlier.

    codex-rs/cli/src/login.rs has clear_existing_auth_before_login(), and both browser ChatGPT login and device-code login call it before starting the replacement flow. That helper calls logout_with_revoke(...).

    The auth manager's logout_with_revoke path first loads the existing stored auth, calls revoke_auth_tokens(...) for it, and then deletes the auth stores. Revocation failure is only warned about; local logout still proceeds. So by the time a later device-code/token exchange fails, the previous login can be gone in two independent senses:

    1. its local credential record has been deleted; and
    2. its refresh token may already have been revoked server-side.

    That means a temp-file + atomic rename at the eventual save_auth site is not sufficient by itself: restoring the old auth.json after failure can restore a credential whose refresh token was intentionally revoked at flow start.

    I would make replacement login transactional at the login lifecycle, not only at the file-write layer: acquire and validate the replacement credentials first while leaving the current auth untouched; persist the replacement successfully to the configured auth backend; only after that commit should any cleanup/revocation of the superseded credential occur (and cleanup failure should not roll back the now-valid replacement).

    A regression that catches the current ordering specifically:

    • seed valid ChatGPT auth A;
    • start codex login --device-auth (and separately browser login), then force a late replacement-flow failure after startup;
    • assert A is still present and has not had its revoke endpoint called;
    • success case: acquire B, make B durably current, then assert cleanup/revocation of A occurs only after B's persistence succeeds;
    • persistence-failure case: if saving B fails, A remains current/unrevoked.

    The same invariant should be exercised for every configured AuthCredentialsStoreMode rather than being expressed only as an auth.json rename property, because the destructive helper is above the storage backend.

    This keeps explicit codex logout destructive while making codex login a replacement transaction instead of logout-then-hope-login-succeeds.

  3. trinity0506 commented on Sep 18, 2026

    @trinity0506
    Author

    Thanks — I verified this against main (7498521d288b) and agree that the file-level sketch is the wrong boundary.

    Concretely:

    • codex-rs/cli/src/login.rs:122 — clear_existing_auth_before_login() calls logout_with_revoke(...) and only tracing::warn!s on failure (:128). It runs before the replacement flow in login_with_chatgpt (:147), run_login_with_device_code (:335) and run_login_with_device_code_fallback_to_browser (:386).
    • codex-rs/login/src/auth/manager.rs:979 — logout_with_revoke() loads the stored auth, calls revoke_auth_tokens(...) (failure only warned, :996), then logout_all_stores(...).

    So the superseded credential can be gone in both senses described above, and restoring auth.json on a late failure may restore an already-revoked refresh token. Superseding the "minimal fix sketch" in the issue body with the lifecycle version:

    1. acquire + validate the replacement credentials while the current auth stays untouched and unrevoked;
    2. persist the replacement durably for every AuthCredentialsStoreMode (not as an auth.json rename property);
    3. only after (2) commits, revoke/clean up the superseded credential, and cleanup failure must not roll back the now-valid replacement.

    Field evidence from our side (headless Linux server, codex-cli 0.153.4): after codex login --device-auth was started and the flow failed late at the token exchange (error sending request for url (https://auth.openai.com/oauth/token)), auth.json was already absent while the flow was still running, and codex login status reported Not logged in. We did not capture whether the revoke endpoint was called, so we cannot confirm the server-side half from our logs.

    One adjacent asymmetry worth checking while the invariant is being moved: run_login_with_api_key (codex-rs/cli/src/login.rs:204) does not go through clear_existing_auth_before_login at all — it calls login_with_api_key directly and overwrites the store, i.e. the previous ChatGPT tokens are dropped locally without being revoked.

  4. 84dnnvbdvp-debug commented on Sep 19, 2026

    @84dnnvbdvp-debug

    I checked the adjacent paths on current main, and the asymmetry is broader than only --with-api-key.

    run_login_with_api_key calls login_with_api_key directly; that function constructs a fresh AuthDotJson containing only the API key and calls save_auth. Likewise, run_login_with_access_token calls login_with_access_token without clear_existing_auth_before_login; both PAT and agent-identity branches construct a fresh replacement AuthDotJson and then save_auth. So a prior ChatGPT token set is replaced locally on those successful transitions without going through revoke_auth_tokens.

    That suggests the transactional repair should define post-commit cleanup by credential transition, rather than mechanically moving the existing logout_with_revoke call after every successful login:

    • ChatGPT A -> ChatGPT B: acquire/validate B, persist B, then clean up/revoke A.
    • ChatGPT A -> API key / PAT / agent identity: after the new credential is durable, explicitly decide whether A's refresh token should be revoked or intentionally left valid; today the local record is replaced while the server-side token can remain live.
    • API key / PAT / agent identity -> ChatGPT B: preserve the old local credential until B is durable, but there may be no refresh token to revoke afterward.
    • replacement persistence failure: previous local credential remains current and no cleanup/revocation runs.

    I would make those transitions a table-driven regression family across AuthCredentialsStoreMode. The important invariant is still “never destroy the only working credential before the replacement commits,” but the cleanup action is credential-type-specific. Otherwise a fix for the reported destructive ChatGPT re-login path could accidentally preserve the current stale-server-token asymmetry or revoke something that was never revocable in the first place.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    CLIIssues related to the Codex CLIauthIssues related to authentication and accountsbugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions