Repository navigation
[macOS] Freshly issued refresh token rejected as refresh_token_invalidated ~10s later, reproduced with a single local process (trigger: opening a thread) #40541
Description
Edited for concision. An earlier version blamed the app-server for not adopting new credentials; that was wrong and is retracted (comment).
TL;DR
- Severity: P0. Sign in succeeds, then opening a thread signs you out 6–17 s later, indefinitely. It is impossible to do anything in Codex Desktop until this is resolved. Still present after updating to 26.820.60940 / core 0.150.0-alpha.8. 20+ open issues in the past week report the same thing on macOS and Windows (list at the bottom).
- Cause: the Desktop calls
getAuthStatus{refreshToken:true}on thread open, which forces a refresh-token rotation, and the auth server rejects the just-issued token withrefresh_token_invalidated. - Reproduced with one local
codexprocess and no intervening refresh POST, so the invalidation is server-side (or the closed-source Desktop shell) — not in this repo. - Client code doesn't cause it but amplifies it: it rotates on every thread open with no freshness check. Hardening suggestions below.
- In-app feedback thread:
01a036de-1b6d-71d0-8de3-e1595279a0dd
What version of the Codex App are you using (From "About Codex" dialog)?
26.818.61809 (build 7019) — bundled core codex-cli 0.149.0-alpha.4.3
What subscription do you have?
ChatGPT Pro
What platform is your computer?
macOS 26.6.2 (build 25G83), Apple Silicon (arm64)
What issue are you seeing?
One process, 10 seconds, freshly issued token:
21:23:06 oauth token exchange succeeded status=200 OK
21:23:07 Reloading auth
21:23:07 Reloaded auth, changed: false <-- logging artifact, see below
21:23:16 app-server request: thread/resume <-- user opened a thread
21:23:16 Reloading auth for account <account-C>
21:23:16 Reloaded auth, changed: false
21:23:16 Refreshing token
21:23:16 ERROR Failed to refresh token: 401 Unauthorized
{"error":{"code":"refresh_token_invalidated", ...}}
Why the token presented at :16 is provably the one issued at :06
- Reaching
Refreshing tokenmeans the guarded reload returnedReloadedNoChange, i.e.auths_equal_for_refreshwas true — for ChatGPT that compares the fullauth.jsoncontents (manager.rs:2471-2473). Cache and disk were byte-identical. - The cache was loaded at
:07from the file written by the:06exchange (persist is awaited before the success redirect,server.rs:437-457). - The only refresh-grant POST in the codebase (
manager.rs:1583) is reachable only through a path that always logsRefreshing tokenfirst (manager.rs:2818). No such line exists from this pid between:07and:16.
Caveat: the Desktop shell is closed-source and could present the same refresh token itself — "one codex process" ≠ "one client".
The changed: false log line is misleading
CodexAuth::eq (manager.rs:154-163) compares only the auth mode for ChatGPT. set_cached_auth logs changed from that coarse comparison while installing the new auth unconditionally (manager.rs:2575-2582); the content-sensitive auth_changed_for_refresh is computed but never logged. So a ChatGPT→ChatGPT re-login always logs changed: false on a fully successful adoption.
Client-side amplifiers (not causes)
- Unconditional rotation.
refresh_token_if_requested(account_processor.rs:1019-1032) POSTs wheneverrefreshToken:trueis passed — no freshness check, even though therefreshToken:falsepath already gates onshould_refresh_proactively. This log: 256 rotations vs 3 skips. - No cross-process safety.
refresh_lock(manager.rs:2045) is per-process;FileAuthStorage::save(storage.rs:206-223) is truncate+write, and torn reads are silently cached asNone(manager.rs:2559-2560). - Stranded after account switch. A process whose cached account no longer matches
auth.jsonhitsReloadOutcome::Skippedforever (manager.rs:2446-2452) — 128 occurrences here — and the error is never recorded, sogetAuthStatuskeeps reporting the stale account. - Three account ids were active on this machine that day; every failing cycle postdates the third. Correlation only (cf. Codex Windows: fresh OAuth refresh token immediately invalidated when two ChatGPT accounts are signed in on the web #39199).
What steps can reproduce the bug?
- Sign in to Codex Desktop on macOS.
- Open an existing thread.
- Signed out (
refresh_token_invalidated). Sign in again → repeats.
Pull the same evidence from any affected machine
sqlite3 -readonly "file:$HOME/.codex/logs_2.sqlite?mode=ro" "
SELECT datetime(ts,'unixepoch','localtime'), process_uuid, feedback_log_body
FROM logs
WHERE feedback_log_body LIKE '%token exchange succeeded%'
OR feedback_log_body LIKE '%Reloading auth for account%'
OR feedback_log_body LIKE '%Refreshing token%'
OR feedback_log_body LIKE '%account id mismatch%'
OR feedback_log_body LIKE '%refresh_token_invalidated%'
ORDER BY ts;"What is the expected behavior?
A refresh token issued seconds earlier should not be rejected, and opening a thread should not force a rotation at all.
Suggested hardening (none is the root cause):
- Log
auth_changed_for_refreshnext tochanged(manager.rs:2581) — one line. - Make
refreshToken:truehonorshould_refresh_proactively(or a short min interval) instead of always POSTing. - temp-file+rename in
FileAuthStorage::save; file lock around refresh→persist. - On
Skipped, surface an "account switched — sign in again" state instead of stranding the process. - If the POST succeeds but persist fails (
manager.rs:3019-3025), keep the rotated token in memory rather than losing it.
Additional information
Latent, probably unrelated: AutoAuthStorage (storage.rs:431-450) — save() falls back to the file on keyring failure without clearing the stale keyring entry, and load() prefers the keyring, so later loads return old tokens indefinitely. Worth its own issue.
Other reports of the same bug
Same mechanism — a fresh refresh token rejected as refresh_token_invalidated / auth lost on thread open or right after login:
- macOS: [macOS][26.814.41407] Opening an existing conversation invalidates ChatGPT auth and redirects to sign-in #39162 (58 comments), Repeated sign-in screen appears after completing a response or opening an existing session #39803 (18), Desktop (macOS): session refresh token revoked server-side seconds after sign-in when resuming a thread on gpt-daybreak-blue-latest — repeated forced-logout loop #39886, Codex Desktop macOS: opening any session causes auth token loss and returns to Sign In after configuring Remote Connection #39684, [macOS 26.818.41705] Thread resume signs out desktop — rotated refresh token never persisted to auth.json, fresh login invalidated in 76s #40267, Desktop app (macOS): logged out after every conversation — "Missing valid access token or actor biscuit", no token refresh attempted #40395, Desktop app repeatedly signs out with "Missing valid access token or actor biscuit" / 401 Unauthorized — not fixed by reinstall #40373, ChatGPT Desktop 26.818.41705: valid OAuth token rejected with 401 "Missing valid access token or actor biscuit" on every session, started immediately after auto-update #40143, Codex Desktop repeatedly logs me out after completing responses #40664, Login session repeatedly expires #40242
- Windows: [Windows 26.814] Opening an existing thread signs out a personal Pro account after workspace-only settings 401 #39189 (22), Windows desktop app loses ChatGPT authentication within 15–40 seconds after enabling Advanced Account Security; Codex CLI remains logged in #39170 (15), Windows Stable: opening a thread immediately invalidates fresh OAuth refresh token with Advanced Account Security enabled; Beta works #39696 (12), Codex Stuck in Login Loop Windows 11 #40036 (12), Windows ChatGPT/Codex Desktop loses ChatGPT access token after successful login and redirects to login #39491 (10), Windows Desktop: fresh ChatGPT refresh token is rejected as refresh_token_invalidated on its first isolated refresh, causing false logout after unrelated 401 #39925, [Windows][26.818.3698.0] Desktop App Infinite OAuth Invalidation Loop, Renderer Deadlock & Black Screen under High Subagent Concurrency #39966, Codex Windows: fresh OAuth refresh token immediately invalidated when two ChatGPT accounts are signed in on the web #39199, [Windows][26.818.8289.0] Codex Desktop loses ChatGPT auth while CLI and server session remain active #40632, Codex Desktop for Windows – authentication loop after opening local project #40699, [Windows][Codex Desktop 26.820.60940] Auth loop followed by intermittent Timeout and "no rollout found for thread id" #40704 (on 26.820.60940), Windows Desktop loses access token and returns to login after /accounts/{account_id}/settings 401 #40374, [Windows] Codex Desktop login succeeds, then /accounts/{id}/settings 401 causes auth_token_missing and a reauthentication loop (26.820.7780.0) #40761, Windows desktop: project chat titles stay stale and account settings request returns 401 after clean reset #40603, Windows Desktop 26.818.5229.0 clears a valid second-account token and enters a 401 restart loop #40074
- Onset: Custom model provider that worked in 0.148 causes 401 in 0.150.1; removing model_provider = "def" fixes it. #40138 (401s begin on update 0.148.0 → 0.149.0-alpha.4.1)
Roughly 200 user comments across these in eight days, with no maintainer response on any of them as of this edit.
Temporary workaround for Desktop users (patched core, tested on both 0.149.0-alpha.4.3 and 0.150.0-alpha.8): see this comment.
Account ids replaced with placeholders. Full log extract available privately on request.
Activity
github-actions commentedon Aug 25, 2026
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
[-][macOS] app-server does not adopt tokens from its own OAuth exchange ("Reloaded auth, changed: false"), then refreshes with the stale token → refresh_token_invalidated within seconds[/-][+][macOS] Freshly issued refresh token rejected as refresh_token_invalidated ~10s later, reproduced with a single local process (trigger: opening a thread)[/+]Suhaibinator commentedon Aug 25, 2026
@cooper-oai @pakrym-oai @etraut-openai — apologies for the direct ping; I know your time is scarce, so tagging you specifically because you wrote the code paths involved (
should_refresh_proactively,reload_if_account_id_matches/refresh_token_if_requested, andauths_equal_for_refresh+ #15798).Flagging priority: this makes the Codex desktop app completely unusable right now — every thread open signs the user out within ~10 s, and re-signing in doesn't help. Several open issues (#40267, #39803, #39162, #39925) describe the same symptom on the current build, and none have a maintainer response yet.
The TL;DR at the top of the issue is five bullets. The short version: a refresh token issued seconds earlier is rejected as
refresh_token_invalidatedwith one local process and no intervening refresh POST, which points server-side (or at the Desktop shell). The client-side hardening suggestions (mainly: don't force a rotation on everygetAuthStatus{refreshToken:true}) would at least shrink the blast radius.Happy to share the full log extract privately or test a build. Thanks.
Aodaruma commentedon Aug 25, 2026
Independent reproduction on the same current build, with an additional trigger and endpoint-level evidence.
Environment:
26.818.61809(build7019), bundledcodex-cli 0.149.0-alpha.4.3hostId=local); no Remote Control host involvedI reproduced the signed-out transition twice today:
07:40:57Z; opening Quick Chat at07:46:04Zwas followed byGET /backend-api/accounts/:param/settingsreturning 401 withhadToken=true, thenhasToken=false / auth_token_missingat07:46:06Z.12:18:34Z; an ordinary localthread/resumesucceeded at12:24:02Z, followed bymaybe_resume_success, then the same account-settings 401 at12:24:03Z. The forced OAuth refresh returnedrefresh_token_invalidated, and the app changed toauth_token_missingat12:24:04Z.The access token issued during the second login was valid until 2026-09-04, so this was not normal access-token expiry. The persisted credential file still contained access, ID, and refresh token fields after the UI transitioned to signed out.
Failed refresh request metadata (no token or account data):
x-request-id: 79cb6283-a741-4d20-87d6-5bf209a873ddcf-ray: a30a92506e578ec0-NRTThis confirms the issue is not Remote Control-specific and can be triggered by Quick Chat as well as local thread resume. It also confirms the sequence in which the settings-endpoint 401 causes a forced refresh and then an app-wide auth drop. Please consider preserving/classifying the settings response before treating it as token expiry, and avoid replacing the app-wide auth state with
auth_token_missingwhile a still-valid access token remains.Raw logs contain local paths and conversation/account metadata, so I am not posting them publicly. I can provide a sanitized timeline or private log excerpts if needed.
Suhaibinator commentedon Aug 25, 2026
Temporary workaround for Desktop users (tested, working on my machine)
Since the invalidation is server-side, the only client-side lever is to stop rotating the refresh token when nothing needs it. The access token issued at login stays valid after the refresh token is killed (authenticated
GET /backend-api/codex/models→200at +3 min and +6 min after arefresh_token_invalidated401), and it has ~10 days of validity, so skipping the forced rotation is enough to keep the app usable.The change —
823fd73f(16 source lines + tests, on top ofmain):AuthManager::refresh_token_if_stale(): no-op when the cached ChatGPT access token isn't near expiry (reuses the existingshould_refresh_proactivelygate); otherwise falls through torefresh_token().refresh_token_if_requested(thegetAuthStatus{refreshToken:true}/account/readpath) calls it instead ofrefresh_token(). The 401-recovery path is unchanged, so a genuinely rejected access token still triggers a real refresh.Result on this machine — after swapping the patched core into Codex Desktop
26.818.61809, five thread opens produced fiveSkipping token refresh because the access token is not near expiry.lines, zero calls to/oauth/token, zero 401s, no sign-out. Before the patch, every thread open was a 401.To try it (macOS, Apple silicon; needs a Rust toolchain; at your own risk):
Notes:
rust-v0.149.0-alpha.4.3tag your app bundles plus the fix, with the version string set to match. Check your bundled version first:/Applications/ChatGPT.app/Contents/Resources/codex --version. For a different version, cherry-pick823fd73fonto that tag.cpfails withOperation not permitted. Don't usecp -fbefore you have that permission — it unlinks the target before the write is refused.cp ~/codex.orig /Applications/ChatGPT.app/Contents/Resources/codex.TOKEN_REFRESH_INTERVAL), and that will fail until the server side is fixed — so expect one sign-in a week instead of one per thread open.For maintainers — three more single-process reproductions this morning, all
refresh_token_invalidated≤11 s after issue, withopenai-processing-ms≈ 100 (a real server decision, not edge).x-request-ids:9161acb5-0ad9-4eac-abf3-40c8df98b9b3(13:34:19Z),1810536b-0bd4-4450-824f-d7619046b52a(13:35:32Z),501910d8-932c-4936-a7b7-0c5a9f290fd4(13:38:24Z). Also observed: the shell'sdesktop-authgetAuthStatusimmediately after login does not force a refresh; the one fired ~1 s afterthread/resumedoes — so the forced rotation is specifically tied to opening a thread.Skip forced token refresh while the ChatGPT access token is fresh
bazhahei123 commentedon Aug 26, 2026
I can reproduce what appears to be the same issue on Windows.
Environment
26.820.609400.150.0My logs showed a successful OAuth token exchange (
200 OK), followed shortly afterwards by:Refreshing token→401 Unauthorized→refresh_token_invalidatedSimply deleting
~/.codex/auth.jsonand signing into Codex again did not fix the issue. The newly issued credentials would still become invalid shortly after login.However, the following sequence has resolved the authentication loop for me so far:
~/.codex/auth.json.After doing these steps in this order, the authentication loop stopped.
This may be relevant to the observation in this issue about multiple account IDs and to #39199. It looks possible that the browser ChatGPT session/account state and the locally stored Codex OAuth credentials can get out of sync, resulting in a newly issued refresh token being invalidated.
I still have the
logs_2.sqlitelogs from the failing state and can provide additional timestamps/log entries if useful.s1n9t9b9r commentedon Aug 26, 2026
Thank you @Suhaibinator , it worked for me.
sanieakhtar commentedon Aug 26, 2026
Facing the same issue after redownloading the the app as required after the npm attack. Every time I click a button or send a message or do anything in the ChatGPT app, it logs itself out and wants to re-login over and over again. I already uninstalled the app, downloaded a fresh installer and tried again. No error messages are shown. Not critical for me so will not be trying the workaround and will wait for a fix.
no-active-thread-01a03ed8-278b-77b2-807f-af15a5cc7cd6
Suhaibinator commentedon Aug 26, 2026
This is literally a P0 (or at least P1) bug, why aren't they prioritizing this...
etsabary commentedon Aug 26, 2026
Independent macOS confirmation of the browser-sign-out reset sequence
I was experiencing the same repeated authentication loop on:
macOS Tahoe 26.5.2, Apple Silicon
Codex/ChatGPT Desktop 26.820.60940
Sign-in itself succeeded, but opening a Codex thread caused the app to lose authentication and return to the sign-in screen. Re-signing in repeatedly did not resolve it.
The sequence that has resolved it for me so far was:
Fully quit the Codex/ChatGPT desktop app.
Sign out of ChatGPT in the web browser.
Move the local Codex credential file aside:
mv ~/.codex/auth.json ~/.codex/auth.json.before-desktop-reset
Relaunch the desktop app.
Complete a fresh ChatGPT sign-in through the desktop app.
After confirming Codex Desktop was functioning, sign back into ChatGPT in the browser.
So far the desktop app has remained authenticated through:
normal Codex tasks;
reopening existing threads;
opening a new thread;
fully quitting/relaunching the desktop app.
I cannot determine which step was decisive, so I am not claiming this establishes the root cause or a general fix. The notable difference from my previous unsuccessful reauthentication attempts was explicitly signing out of the browser ChatGPT session before moving aside ~/.codex/auth.json and then performing the fresh desktop OAuth login.
This appears consistent with the workaround reported above where synchronizing/resetting the browser session and local Codex OAuth state stopped the loop.
I’ll update this comment if the failure returns.
HaroldZhong commentedon Aug 26, 2026
Thanks for this. Your five steps worked perfectly. I went through all the actions that triggered the sign-out behavior earlier, and I was not able to reproduce the issue again.
metacircu1ar commentedon Oct 7, 2026
I reproduced the
FileAuthStorage::savetorn-read hazard mentioned here with a deterministic regression test. Saving shorter credentials while another reader has the old file open produces invalid JSON.Proposed fix and tests: [metacircu1ar/codex#1](metacircu1ar#1).
The fix uses atomic file replacement. All 256 tests pass in release mode on macOS; Windows compilation passes, but native Windows execution remains unverified.
This addresses file publication only—it does not establish the cause of the reported server-side token invalidation.