openclaw - 💡(How to fix) Fix Codex OAuth: 401 `token_expired` on runtime refresh while `--probe` succeeds with same profile (v2026.5.12)

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

On v2026.5.12 (f066dd2), openclaw models auth login --provider openai-codex writes a fresh credential to the encrypted on-disk store (mtime-verified), and openclaw models status --probe --probe-provider openai-codex confirms that same profile works against chatgpt.com/backend-api (ok in 8.5s). Yet every gateway turn that needs to refresh the OAuth token fails with HTTP 401 token_expired from ChatGPT, regardless of agentRuntime.id choice. The failure is isolated to the runtime token-refresh path; the access_token-direct path is healthy.

Distinct from #79021 (multi-profile + stale lastGood). Single profile, lastGood correctly bound, ChatGPT-side account verified healthy in the Connected Apps dashboard with subscription quota largely untouched. Earlier comment on #79021 — at the time I expected we shared its surface; further data has separated them.

Error Message

[agents/harness] Codex agent harness failed; not falling back to embedded PI backend [diagnostic] lane task error: lane=main durationMs=988 error="Error: OAuth token refresh failed for openai-codex: OpenAI Codex token refresh failed (401): { "error": { "message": "Could not validate your token. Please try signing in again.", "type": "invalid_request_error", "param": null, "code": "token_expired" } }" [model-fallback/decision] decision=candidate_failed requested=openai/gpt-5.5 candidate=openai/gpt-5.5 reason=auth_permanent next=openai/gpt-5.4

Root Cause

Leave Codex on the shelf via configured fallback (anthropic/claude-opus-4-7). The PI-runtime workaround documented in providers/openai doesn't help here because OpenClaw's auth fall-through ends up using openai:default (env OPENAI_API_KEY) automatically when the Codex OAuth refresh path 401s — that's a working OpenAI API-key turn, but it's not the subscription path the operator configured.

Fix Action

Fix / Workaround

  1. Existing v2026.5.7 install with openai-codex/* refs and Codex OAuth healthy.
  2. Upgrade to v2026.5.12. doctor --fix migrates legacy refs to canonical openai/* and adds provider/model-scoped agentRuntime.id: "codex" (expected, per #77731 / #78533 / #78570).
  3. Every primary turn now fails with Model login expired on the gateway for openai-codex. Fallback chain catches each turn.
  4. Run openclaw models auth login --provider openai-codex. Browser OAuth flow completes successfully; profile metadata expires is updated and credential file mtime bumps.
  5. openclaw gateway restart.
  6. Same failure continues on every subsequent turn. Re-running 4–5 does not change the outcome.

Workaround in our setup

Leave Codex on the shelf via configured fallback (anthropic/claude-opus-4-7). The PI-runtime workaround documented in providers/openai doesn't help here because OpenClaw's auth fall-through ends up using openai:default (env OPENAI_API_KEY) automatically when the Codex OAuth refresh path 401s — that's a working OpenAI API-key turn, but it's not the subscription path the operator configured.

Code Example

"auth": {
  "profiles": {
    "openai-codex:[email protected]": { "provider": "openai-codex", "mode": "oauth" }
  },
  "order": {
    "openai": ["openai-codex:[email protected]"]
  }
},
"agents": {
  "defaults": {
    "model": {
      "primary": "openai/gpt-5.5",
      "fallbacks": ["openai/gpt-5.4", "anthropic/claude-opus-4-7"]
    },
    "models": {
      "openai/gpt-5.5":      { "params": { "contextTokens": 400000  }, "agentRuntime": { "id": "codex" } },
      "openai/gpt-5.4":      { "params": { "contextTokens": 1000000 }, "agentRuntime": { "id": "codex" } },
      "openai/gpt-5.4-mini": { "params": { "contextTokens": 272000  }, "agentRuntime": { "id": "codex" } }
    }
  }
},
"plugins": {
  "allow": [..., "codex"],
  "entries": { "codex": { "enabled": true } }
}

---

[agents/harness] Codex agent harness failed; not falling back to embedded PI backend
[diagnostic] lane task error: lane=main durationMs=988
  error="Error: OAuth token refresh failed for openai-codex:
  OpenAI Codex token refresh failed (401): {
    "error": {
      "message": "Could not validate your token. Please try signing in again.",
      "type": "invalid_request_error",
      "param": null,
      "code": "token_expired"
    }
  }"
[model-fallback/decision] decision=candidate_failed
  requested=openai/gpt-5.5 candidate=openai/gpt-5.5
  reason=auth_permanent next=openai/gpt-5.4

---

$ openclaw models status --probe --probe-provider openai-codex
Auth probes
┌──────────────────────┬────────────────────────────────────────────────────────────┬────────────┐
ModelProfileStatus├──────────────────────┼────────────────────────────────────────────────────────────┼────────────┤
│ openai-codex/gpt-5.4 │ openai-codex:aaajiao@gmail.com (aaajiao@gmail.com) (oauth) │ ok · 8.5s  │
└──────────────────────┴────────────────────────────────────────────────────────────┴────────────┘
OAuth/token status
- openai-codex usage: 5h 100% left ⏱18m · Week 97% left ⏱4d 2h
  - openai-codex:aaajiao@gmail.com (aaajiao@gmail.com) ok expires in 10d

---

$ openclaw models auth login --provider openai-codex --device-code
OpenClaw does not recognize option "--device-code".
RAW_BUFFERClick to expand / collapse

Summary

On v2026.5.12 (f066dd2), openclaw models auth login --provider openai-codex writes a fresh credential to the encrypted on-disk store (mtime-verified), and openclaw models status --probe --probe-provider openai-codex confirms that same profile works against chatgpt.com/backend-api (ok in 8.5s). Yet every gateway turn that needs to refresh the OAuth token fails with HTTP 401 token_expired from ChatGPT, regardless of agentRuntime.id choice. The failure is isolated to the runtime token-refresh path; the access_token-direct path is healthy.

Distinct from #79021 (multi-profile + stale lastGood). Single profile, lastGood correctly bound, ChatGPT-side account verified healthy in the Connected Apps dashboard with subscription quota largely untouched. Earlier comment on #79021 — at the time I expected we shared its surface; further data has separated them.

Environment

  • OpenClaw 2026.5.12 (f066dd2)
  • macOS host running gateway in Linux VM (systemd user unit)
  • Telegram primary channel
  • ChatGPT subscription, chatgptPlanType: "prolite", single OAuth profile openai-codex:[email protected]
  • ChatGPT-side: account verified active at chatgpt.com (no billing warnings; OAuth grant still listed in Connected Apps)
  • Quota: 5h 100% left ⏱18m · Week 97% left ⏱4d 2h (per openclaw models status)

Config (per docs quickstart)

"auth": {
  "profiles": {
    "openai-codex:[email protected]": { "provider": "openai-codex", "mode": "oauth" }
  },
  "order": {
    "openai": ["openai-codex:[email protected]"]
  }
},
"agents": {
  "defaults": {
    "model": {
      "primary": "openai/gpt-5.5",
      "fallbacks": ["openai/gpt-5.4", "anthropic/claude-opus-4-7"]
    },
    "models": {
      "openai/gpt-5.5":      { "params": { "contextTokens": 400000  }, "agentRuntime": { "id": "codex" } },
      "openai/gpt-5.4":      { "params": { "contextTokens": 1000000 }, "agentRuntime": { "id": "codex" } },
      "openai/gpt-5.4-mini": { "params": { "contextTokens": 272000  }, "agentRuntime": { "id": "codex" } }
    }
  }
},
"plugins": {
  "allow": [..., "codex"],
  "entries": { "codex": { "enabled": true } }
}

End-to-end match for docs/plugins/codex-harness and the providers/openai Codex-subscription quickstart.

Reproduction

  1. Existing v2026.5.7 install with openai-codex/* refs and Codex OAuth healthy.
  2. Upgrade to v2026.5.12. doctor --fix migrates legacy refs to canonical openai/* and adds provider/model-scoped agentRuntime.id: "codex" (expected, per #77731 / #78533 / #78570).
  3. Every primary turn now fails with Model login expired on the gateway for openai-codex. Fallback chain catches each turn.
  4. Run openclaw models auth login --provider openai-codex. Browser OAuth flow completes successfully; profile metadata expires is updated and credential file mtime bumps.
  5. openclaw gateway restart.
  6. Same failure continues on every subsequent turn. Re-running 4–5 does not change the outcome.

Gateway log (post fresh re-auth + restart)

[agents/harness] Codex agent harness failed; not falling back to embedded PI backend
[diagnostic] lane task error: lane=main durationMs=988
  error="Error: OAuth token refresh failed for openai-codex:
  OpenAI Codex token refresh failed (401): {
    "error": {
      "message": "Could not validate your token. Please try signing in again.",
      "type": "invalid_request_error",
      "param": null,
      "code": "token_expired"
    }
  }"
[model-fallback/decision] decision=candidate_failed
  requested=openai/gpt-5.5 candidate=openai/gpt-5.5
  reason=auth_permanent next=openai/gpt-5.4

Smoking gun — --probe succeeds with the same profile

$ openclaw models status --probe --probe-provider openai-codex
Auth probes
┌──────────────────────┬────────────────────────────────────────────────────────────┬────────────┐
│ Model                │ Profile                                                    │ Status     │
├──────────────────────┼────────────────────────────────────────────────────────────┼────────────┤
│ openai-codex/gpt-5.4 │ openai-codex:[email protected] ([email protected]) (oauth) │ ok · 8.5s  │
└──────────────────────┴────────────────────────────────────────────────────────────┴────────────┘
OAuth/token status
- openai-codex usage: 5h 100% left ⏱18m · Week 97% left ⏱4d 2h
  - openai-codex:[email protected] ([email protected]) ok expires in 10d

Same openai-codex:[email protected] profile, same chatgpt.com/backend-api backend, same ChatGPT account, 8.5s round-trip success. The --probe path does not appear to perform a preflight OAuth refresh; the gateway runtime path does. The runtime refresh is what 401s.

Both runtime choices fail identically

Runtime configResult
agentRuntime.id: "codex" (Codex app-server harness, default per docs)401 token_expired on every turn
agentRuntime.id: "pi" (PI runtime + internal Codex-auth transport per docs)Same 401 on Codex OAuth refresh → automatic fall-through to next ordered profile → ends up using openai:default (env OPENAI_API_KEY) silently
Legacy openai-codex/* ref (PI runtime, pre-migration shape)Same 401 token_expired
--probe against openai-codex/gpt-5.4 legacy refok · 8.5s

The PI runtime fall-through is consistent with the v5.12 design described in the release notes ("OpenClaw can rotate to the next ordered openai:* API-key profile without changing the selected model"), but it means the subscription-backed path is silently never used in practice while the refresh bug is in place.

On-disk state

  • Auth profile (~/.openclaw/agents/main/agent/auth-profiles.json["openai-codex:[email protected]"]):
    • expires bumps across consecutive re-auth attempts (e.g. 17796562989511779658794746)
    • oauthRef.id is stable (ba8c7510480b662f02a8940805458d0b) — consistent with same-logical-profile, updated credential blob
  • Credential blob (~/.openclaw/credentials/auth-profiles/ba8c7510…json):
    • mtime tracks the most recent auth login invocation
    • Size 3111 bytes; structure { version, profileId, provider, encrypted } with encrypted object ~2969 bytes (encrypted-at-rest, opaque from outside)
    • Size is consistent with a full access_token + refresh_token + metadata payload plus AEAD overhead

What this rules out

  • Not #79021 (multi-profile + stale lastGood): single profile, lastGood.openai-codex correctly points at it.
  • Not migration breakage: refs migrated to canonical, declarations carry agentRuntime.id, auth.order.openai set, codex plugin enabled — exactly the docs quickstart.
  • Not auth.order misconfiguration: explicit auth.order.openai: ["openai-codex:[email protected]"].
  • Not gateway in-memory cache: gateway restarted between attempts; failure persists across cold starts.
  • Not credential store not being updated: mtime confirms fresh write on every re-auth.
  • Not a dead token: --probe round-trips against chatgpt.com/backend-api with the same profile in 8.5s.
  • Not a ChatGPT account issue: account active, OAuth grant present and not revoked, quota nearly full on both rolling windows.
  • Not runtime-specific: identical 401 from Codex app-server harness AND PI runtime + Codex-auth transport.

Hypotheses

  1. Refresh-token rotation not persisted in the encrypted blob: the refresh flow consumes the current refresh_token on first use, gets back a new access_token plus a rotated refresh_token, but only the access_token (or the wrapper file metadata) lands back in the encrypted blob. Every subsequent runtime call then sends the already-consumed refresh_token and ChatGPT 401s. Explains why mtime is fresh (file IS rewritten) while the refresh path is poisoned. Closest matching v5.12 release-note language: "Codex harness: keep oauthRef-backed Codex OAuth profiles usable and stop high-confidence app-server OAuth refresh invalidation from retry-spamming raw token-refresh errors without turning entitlement or usage-limit payloads into re-auth prompts" — code path was actively changed in this release.

  2. Runtime always-refresh preflight regardless of access_token validity: the runtime path triggers an OAuth refresh before every turn even when the access_token has 10 days of life remaining (per profile expires). If the refresh helper has any bug or scope mismatch, every turn fails; --probe, which uses the access_token directly, is unaffected. This would explain the runtime-vs-probe divergence even if refresh_token rotation were fine.

  3. chatgptPlanType: "prolite" entitlement payload misclassified as token_expired: a non-Plus/Pro ChatGPT plan whose OAuth grants are accepted by chatgpt.com/backend-api for direct API calls (probe ok) but refused by the OAuth refresh endpoint with code: token_expired. The v5.12 fix attempting to "stop turning entitlement or usage-limit payloads into re-auth prompts" might not cover this exact response shape.

Hypothesis 1 is the cheapest to verify from inside the harness: instrument the refresh path to log whether the response contained a rotated refresh_token and whether it was persisted back. Hypothesis 2 is the next cheapest: probe the refresh trigger condition (is it actually validity-gated?).

Available on request

  • Redacted JSON shape of auth-profiles.json["openai-codex:…"] (already partially in this report; oauthRef indirect path confirmed)
  • Diff of the encrypted blob structure across two consecutive re-auth invocations
  • Gateway logs of openclaw models auth login itself
  • A wire-level capture of the request that gets 401'd, if it would help isolate

Happy to round-trip on any of these. As filer, I'm a long-running maintainer-credited user (@aaajiao) — recent thanks in v5.2 / v5.6 release notes for #75220 / #74860 / #78407 follow-ups — so happy to keep iterating.

Workaround in our setup

Leave Codex on the shelf via configured fallback (anthropic/claude-opus-4-7). The PI-runtime workaround documented in providers/openai doesn't help here because OpenClaw's auth fall-through ends up using openai:default (env OPENAI_API_KEY) automatically when the Codex OAuth refresh path 401s — that's a working OpenAI API-key turn, but it's not the subscription path the operator configured.

Small docs/CLI mismatch (worth a follow-up)

docs/providers/openai#codex-subscription documents openclaw models auth login --provider openai-codex --device-code for "headless or callback-hostile setups". On v2026.5.12 (f066dd2) the CLI rejects this:

$ openclaw models auth login --provider openai-codex --device-code
OpenClaw does not recognize option "--device-code".

openclaw models auth login --help shows only --method, --provider, --set-default, and -h. None of the obvious --method device-code | device-flow | device | oauth-device values are recognized. Either the flag wasn't shipped in v5.12 or the docs are ahead of the release. Happy to file separately if useful — flagging here for context because device-code would have been my next diagnostic for this bug.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - 💡(How to fix) Fix Codex OAuth: 401 `token_expired` on runtime refresh while `--probe` succeeds with same profile (v2026.5.12)