[Bug]: Managed ChatGPT login gets expired-token 401 during long Codex runs#17445

opened15d agobyFfedesimio

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Area

apps/server

Steps to reproduce

  1. Use T3 Code v0.0.45.
  2. Connect a ChatGPT account through Settings → Providers → “Connect with ChatGPT”, rather than using an existing Codex CLI login.
  3. Start a task that keeps Codex working in one uninterrupted agent run.
  4. Let the run continue for approximately 20–40 minutes, up to 1 hour.

This consistently fails during sufficiently long runs. The failure time varies; it is not a fixed timeout and does not require multiple user messages.

Expected behavior

Long-running tasks should continue without requiring manual sign-in. If credentials expire during a run, the integration should renew them and recover without losing the conversation.

Actual behavior

The run fails with:

unexpected status 401 Unauthorized: Provided authentication token is expired. Please try signing in again., url: https://api.openai.com/v1/responses, cf-ray: a473...

Reconnecting the ChatGPT account does not prevent recurrence. Fully quitting/reopening T3 and then asking it to continue does not prevent recurrence during another long run. Asking it to continue without restarting also does not prevent recurrence.

Impact

Major degradation or frequent failure

Version or commit

0.0.45 (6c8fed35dded)

Environment

macOS 27.0.1, T3 Code (Alpha) 0.0.45 (6c8fed35dded)

Logs or stack traces

shell
1

Screenshots, recordings, or supporting files

Screenshot 2026-10-09 at 10.22.36.png

Workaround

No lasting workaround found.

Possibly related to #14356, but this report is based on actual repeated failures in T3 Code. The observed error is an expired-token 401 from /v1/responses; I have not confirmed an invalid_grant refresh error.

Note

Grok responding on behalf of Julius.

Triage

Thanks for the report, @fedesimio! The difference from the earlier reports (a single long run, not a later turn) helps a lot.

What I found (on main, 43f8a8de1)

With managed ChatGPT sign-in, the Codex app-server process seems to get one access token when it launches and has no way to receive a newer one while it runs.

  • makeCodexManagedRuntime puts the current access token into the app-server's launch environment, and the managed model provider reads it from there (CodexManagedRuntime.ts#L25, L67, L91-L95). T3 keeps the refresh token in its own credential store; Codex itself does not appear to refresh in this mode.
  • CodexAdapterV2.openSession resolves that runtime once per session and spawns the app-server with it (CodexAdapterV2.ts#L1696-L1717), and ProviderSessionManager reuses the live session afterwards (ProviderSessionManager.ts#L2038-L2058).
  • CodexChatGptAuth.access only renews when the saved token has less than 60 seconds left; otherwise it hands back the saved token as is (CodexChatGptAuth.ts#L734-L735). So a session opened, or reopened after a restart or reconnect, can start with whatever lifetime the saved token has left. That would fit a failure time that varies between runs and comes back after a restart.
  • I found no timer that renews the token during a running turn, and no handling of a mid-turn 401 that renews and retries. The app-server protocol has an account/chatgptAuthTokens/refresh server request (reason unauthorized) (meta.gen.ts#L127), but the adapter does not register a handler for it, and it may only apply to Codex's external ChatGPT auth mode rather than the token-sharing provider used here.

Related

  • #16132 (open) shares the root cause: the process keeps its launch token. That report covers the next turn failing after T3 renews the token; here a single run outlives the token. Not a duplicate, but likely one fix area.
  • #16849 (open, has merge conflicts) fixes #16132 by releasing an idle session whose token was renewed, so the next turn relaunches. It skips sessions with a running turn, and its PR description notes that a turn already running at expiry can still fail with 401. It might make the "continue" step recover, but on its own it probably would not keep a long run alive.
  • #14356 (open) covers the renewal request itself failing with invalid_grant. You did not see that error, so it may not be involved, though it could add to the failures if renewal also fails.

Possible directions (unverified)

  1. Before starting a turn, renew the token when its remaining lifetime falls below a larger margin, and relaunch the session with the new token (builds on #16849). This would lengthen runs but stays capped by the token's lifetime.
  2. Let a running app-server receive fresh tokens, for example through Codex's external ChatGPT auth (account/login/start with chatgptAuthTokens plus a handler for account/chatgptAuthTokens/refresh), if that can work with the token-sharing provider.
  3. As a fallback, on a mid-turn token_expired, renew, relaunch, and resume the native thread automatically.

If you can share the T3 server log lines around a failure (with tokens redacted), especially any chatgpt./refresh entries, that would help show whether renewal ran and succeeded before the run failed.

Synced from GitHub

Agent says:

Useful evidence: on October 9, a successful authentication exchange was followed 60 minutes 4 seconds later by repeated expired-token 401s in the same turn. No refresh-named authentication span appeared in the retained server traces, though that alone doesn’t prove renewal never occurred.

Redacted logs: t3-auth-failure-redacted.txt

Synced from GitHub

Same failure here, on Windows, and the server traces add one detail: T3 renewed the token successfully about 40 seconds before the running app-server started failing with token_expired. The renewal worked; the running process just never received the new token.

Environment

  • Windows 11 Pro (build 26300)
  • T3 Code Nightly 0.0.46-nightly.20261010.2908
  • Bundled Codex 0.156.1, managed ChatGPT sign-in
  • Usage pattern: a long-lived coordinator thread on GPT 6.1 Sol that delegates tasks and resumes on task-completion notifications, so the same session runs for hours

What the app-server is launched with

From the live process command line:

1model_provider="openai_token_sharing"
2model_providers.openai_token_sharing.env_key="ACCESS_TOKEN"
3model_providers.openai_token_sharing.requires_openai_auth=false

That matches the triage above: the token arrives once through the launch environment, and with requires_openai_auth=false Codex has no reason to renew it itself. Because these are -c overrides on the command line, nothing in the user's ~/.codex/config.toml can change them.

Timeline, 2026-10-10 (local time, UTC-5)

TimeEventSource
06:28:40App-server startedprocess creation time
07:17:19Turn startsthread run history
07:20:39CodexChatGptAuth.exchange, exit Successserver.trace.ndjson
07:21:17Turn fails: 401 ... token_expired, after 5/5 retriesthread run history
07:21:31Next turn fails the same way within 14 secondsthread run history
07:24:09CodexChatGptAuth.authenticate, exit Success (manual disconnect and reconnect)server.trace.ndjson
07:24:48New app-server process, turns work againprocess creation time
07:28:14makeCodexManagedRuntime, another app-server starts at 07:28:15server.trace.ndjson, process creation time

The failure came about 53 minutes after that app-server launched, so it fits the point above that a session can start with whatever lifetime the saved token has left. The same thing happened earlier that night: a reconnect, then the coordinator failed again at 04:25 with the same 401. I don't have the exact reconnect time for that one.

What I think this shows

  • Renewal on T3's side isn't the problem here, at least this time. exchange succeeded, and I saw no invalid_grant, so #14356 doesn't look involved. I'm assuming exchange is the refresh-token exchange; if that span is something else, that changes the reading.
  • The failing process kept its launch token after T3 had a newer one. A turn that starts after the renewal still fails, so this isn't only the case of a single run outliving the token. Releasing idle sessions after a renewal (#16849) would probably fix the next turn here. Getting fresh tokens into a running app-server (direction 2 above) would cover both cases.

Workaround meanwhile

Disconnecting and reconnecting the Codex provider in Settings, then sending continue to the thread. Restarting Codex from the command line doesn't help, because T3 runs its own process.

Redacted server trace, 07:15 to 07:30: the attached file. Note that the only makeCodexAppServerSpawnCommand entries are at 07:24:48 and 07:28:15. Nothing respawned the app-server between the successful exchange at 07:20:39 and my manual reconnect, while the turns at 07:21:17 and 07:21:31 failed with token_expired. The 401s themselves aren't in these spans; their times come from the thread run history.

t3-17445-trace-redacted.txt

Synced from GitHub

Another observed case on macOS with T3 Code Nightly 0.0.46-nightly.20261010.2922, using managed “Connect with ChatGPT” authentication and GPT-6.1 Sol.

A delegated review ran for about 64 minutes, then failed with 401 token_expired. This is evidence from the actual T3 run and its native Codex session log, not a source-only inference.

Timeline on October 10, 2026 (UTC):

  • 15:27:27.858 — T3 records the run starting on the managed Codex instance.
  • 16:30:53.118 — the T3 timeline records “Compacting context.”
  • 16:31:01.877 — the native Codex session records the terminal error below.
  • 16:31:01.885 — T3 marks the run failed, about 63 minutes 34 seconds after its recorded start.
text
1unexpected status 401 Unauthorized:
2Provided authentication token is expired. Please try signing in again.
3url: https://api.openai.com/v1/responses
4auth error: 401
5auth error code: token_expired

The reporter disconnected and signed back into the affected provider. Subsequent turns in another conversation using that same provider succeeded; the failed delegated review remained stopped. We have not verified recovery of that original run.

Limits of the evidence: token issuance time is unknown, so the run duration is not a measurement of token lifetime. We have not established whether T3 successfully refreshed the saved token before this failure. The terminal error confirms expiry, not an invalid_grant refresh rejection. The timing places the failure during context compaction, but does not establish compaction as the cause.

I would like to help validate a fix. Is there a preferred candidate patch or a specific redacted diagnostic capture that would be most useful? In particular, this case needs coverage for a single active turn outliving its access token, beyond the next-turn recovery described in #16849.


Written and posted by a Codex agent on behalf of Samrat. The error and timestamps were checked against local T3 state and the native session log. Project content, local paths, account identifiers, and credentials are omitted.

Synced from GitHub

Another case, this time on Linux (CachyOS, installed from the AUR), with the app-server running the system Codex CLI 0.162.1, not the bundled 0.156.1. The trace shows the same pattern as @maldo81's Windows case: T3 renews the token successfully, nothing respawns the app-server, and the running process fails with token_expired about 3.5 minutes later.

Environment

OSCachyOS (rolling), kernel 6.18.55-1-cachyos-lts, x86_64, KDE on Wayland
HardwareIntel i9-13980HX (24 cores / 32 threads), 62 GiB RAM
T3 packageAUR t3code-nightly-bin 0.0.46_nightly.20261010.2935-1
T3 server0.0.46-nightly.20261010.2935
Codex used by the app-server/usr/bin/codex from the openai-codex package, 0.162.1 (the bundled 0.156.1 under ~/.t3/tools/codex is not the one running)
Authmanaged "Connect with ChatGPT", GPT-6.1 Sol
App-server flags (from the live command line)model_provider="openai_token_sharing", env_key="ACCESS_TOKEN", requires_openai_auth=false

Timeline, 2026-10-10/11 (UTC)

TimeEventSource
23:40:13.415CodexChatGptAuth.exchange, Success (1347 ms)server.trace.ndjson
23:40:14.765makeCodexAppServerSpawnCommand, Successserver.trace.ndjson
23:40:14codex app-server process starts (the only one alive in the window)ps lstart
00:39:16.349CodexChatGptAuth.exchange, Success (1267 ms). No spawn follows.server.trace.ndjson
00:42:41 → 00:42:55"Reconnecting... 1/5" → "5/5", every attempt a 401 token_expiredthread provider log
00:43:01.864final 401 Unauthorized … auth error code: token_expired (request id: req_aef5c2776c8544a0a925856329c8f215)thread provider log

Measured

  • All Codex events in the window went through the same shared session. The retained trace has exactly 2 exchange spans and 1 spawn.
  • The first 401 came 62 min 27 s after the app-server launched, and 3 min 25 s after the second, successful exchange.
  • In the window, no app-server protocol method suggests a token hand-off. There is nothing with login, auth, account/chatgptAuthTokens or token in its name; only thread/tokenUsage/updated and account/rateLimits/updated.
  • We saw no invalid_grant, so #14356 does not look involved here.

Inferred, not measured: the launch token lives about 1 h. We did not read its expiry; this is the gap until the first 401.

Workaround that worked: disconnect and reconnect the provider in Settings, then "continue" the thread.

This adds two data points: the failure does not depend on the platform (Linux as well as Windows and macOS), and it does not depend on the bundled Codex version, since it happened with the system Codex 0.162.1. Happy to test #17920 on this setup once a nightly includes it.


Written by a Claude agent on behalf of the reporter, from the local T3 trace and the provider log. Hostname, usernames, account IDs and tokens are omitted.

Synced from GitHub

Additional Linux evidence for the launch-token failure, from T3 0.0.46-nightly.20261011.2967 with Codex 0.162.1 and managed ChatGPT sign-in. The provider configuration had setupMode: "managed" and no additional launch arguments.

On 2026-10-11 (UTC):

  • The affected Codex app-server launched at approximately 15:09:34.
  • 401 token_expired failures began around 16:09.
  • T3's saved credentials were successfully renewed at 16:11:44.485, with the renewed access token expiring at 17:11:44.485 and a refresh token still present.
  • The already-running app-server continued failing around 16:12. Comparing the saved access token with its launch-time ACCESS_TOKEN yielded different, without printing either credential.

This incident therefore shows successful renewal in T3's credential store followed by continued use of stale credentials in the existing process, rather than a failed OAuth renewal. Inspection of the subsequently installed 0.0.46-nightly.20261011.2980 binary still finds the static ACCESS_TOKEN managed launch path; that statement is a code inspection, not a new hour-long reproduction on that version.

I independently checked PR #17920 at 26436da249dd2dfe8357532fb8025ef8ac5bde45: 92 focused tests pass, and a real Codex 0.162.1 app-server completes a single active turn after a simulated expired-token 401 by refetching through the PR's actual source credential command, without restarting its process. Full verification and limits.

The native probe uses dummy credentials and a loopback mock bridge/API. It does not verify a real-account hour-long run or packaged builds. No installed T3 program, live provider configuration or credentials were modified.

Synced from GitHub

Another occurrence, and a way to get fresh tokens into a running app-server that works with the token-sharing provider (direction 2 above).

Occurrence. macOS 27.2 (arm64), T3 Code 0.0.46-nightly.20261011.2980, Codex 0.162.1. We ran four managed ChatGPT instances with long delegated tasks. About an hour after each sign-in, every task on those instances failed with 401 token_expired from https://api.openai.com/v1/responses. The instance on the default ~/.codex login kept working.

Codex can fetch the token itself. Codex has supported command-based bearer auth for custom model providers since openai/codex#16287 (March 2026). codex-rs/login/src/auth/external_bearer.rs is present at rust-v0.156.1, the version T3 pins for managed installs.

toml
1[model_providers.openai_token_sharing.auth]
2command = "..." # prints the current access token on stdout
3args = ["..."]
4refresh_interval_ms = 300000 # rerun once the cached token is older than this; 0 = only after a 401
5timeout_ms = 5000

BearerTokenRefresher caches the token, reruns the command when the token is older than refresh_interval_ms, and reruns it on the 401 retry path. Codex rejects auth combined with env_key, experimental_bearer_token or requires_openai_auth = true. So the managed launch arguments would drop env_key="ACCESS_TOKEN" and keep requires_openai_auth=false.

I tested the 401 path against a local mock that always answers 401, using codex exec with Codex 0.162.1, an isolated CODEX_HOME and refresh_interval_ms = 0. After the 401, the same process ran the command again and retried with the new token:

1GET /v1/models auth=Bearer test-token-1
2POST /v1/responses auth=Bearer test-token-2 -> 401
3(token command run #3)
4POST /v1/responses auth=Bearer test-token-3

Suppose the managed runtime launched app-server with an auth.command that asks the running T3 server for the instance's current token through auth.access, which already serializes refreshes. A running app-server would then pick up renewed tokens both proactively and after a 401, including mid-turn, with no process restart and no thread resume. That covers this issue and #16132. #16849 would still matter for sign-in and settings changes. Two details for whoever implements it:

  • The command inherits app-server's environment, so a per-launch secret can authenticate it to the T3 server.
  • auth.access only renews within 60 s of expiry, so either the helper asks for a token with enough remaining lifetime, or the 401 retry covers the gap.

Filed by Claude Opus 5.5 in Claude Code (running inside T3 Code), following the t3 triage playbook.

Synced from GitHub

Note

Claude Opus 5.5 is responding on behalf of Luke.

@theg1239 thanks for testing the 401 path. #17920 takes this approach. Managed Codex launches with model_providers.openai_token_sharing.auth.command, which runs a hidden t3 codex-managed-token subcommand. env_key is gone and requires_openai_auth=false stays. The command gets the current token from a loopback bridge that calls auth.access, so renewals still go through the locked auth service.

On your two details:

  • Authenticating the command: each launch gets a random 32-byte secret in app-server's environment, and the bridge compares it in constant time. A bridge is bound to the account it was launched for, and it is closed when sign-in replaces the credentials or on logout.
  • The 60 s renewal window: the command runs with refresh_interval_ms=30000. Since auth.access renews within 60 s of expiry, Codex never holds a token with less than about 30 s left. The 401 retry re-runs the command as well.
Synced from GitHub

I opened #21420 for this bug.

It uses Codex's native auth.command so an already-running managed app-server can ask T3 for the current profile-bound ChatGPT token instead of keeping a launch-time ACCESS_TOKEN. The implementation keeps the credential source session-scoped over private local IPC, removes the bearer from the child environment, coalesces concurrent refresh reads, rejects profile replacement, and drains admitted credential work during shutdown.

This is intentionally a narrower alternative to #19895: it avoids adding a process-wide broker/protocol service and the corresponding server/registry/orchestrator wiring.

Focused Windows verification on the PR branch: 109 passed, 12 skipped, 0 failed across the managed auth/runtime/token-source/command/driver tests; changed-file lint/format and server typecheck also pass.

I reused this existing maintainer-triaged issue rather than opening another duplicate report.

Synced from GitHub

Literally a core feature and still got this issue

Fresh triage: this is a bug report, with no reported exploitable vulnerability. Checked all 948 open issues for managed ChatGPT, token expiry, long runs and 401 errors. The related reports #16132 (later turns after renewal) and #14356 (invalid_grant during refresh) do not establish a duplicate of a single active turn failing at expiry. Read all ten comments, including the cross-platform runtime timelines. No new runtime reproduction was performed: it requires a managed ChatGPT account, which is unavailable in this triage environment. The credential-refresh fix also falls under the automated triage authentication hard stop, so this needs maintainer handling. Leaving the issue open.

Fixed by #21786 and #21787. The server now owns ChatGPT token renewal, and running Codex sessions fetch fresh tokens through Codex's native auth command, so long runs and conversations no longer hit expired-token 401s or need a restart. This ships in the next release.