openai-codex provider: multi-turn sessions permanently break with 400 invalid_encrypted_content after an OAuth token refresh
Summary
When the agent model is openai-codex/gpt-5.5 authenticated via a ChatGPT-plan OAuth profile, any multi-turn session works for a while and then every subsequent turn fails silently with HTTP 400 invalid_encrypted_content from the Codex Responses API.
The failure is deterministically triggered by an OAuth access-token refresh. OpenClaw replays encrypted_content reasoning items from earlier turns; those blobs are bound to the access token that produced them, so once the token is refreshed (periodic refresh, or re-auth on gateway restart) the Codex backend can no longer decrypt them and rejects the whole request.
The user receives no reply at all — the run ends as an empty non_deliverable_terminal_turn.
Environment
- OpenClaw
2026.5.18 (build 50a2481, trace gitSha 39ee0dd)
- macOS 26.2 (arm64), Node
v25.9.0
- Provider
openai-codex, model gpt-5.5, api openai-codex-responses, harness pi
- Auth: ChatGPT-plan OAuth (
auth_mode=chatgpt, plan pro)
- Gateway mode
local
Steps to reproduce
- Configure an agent with
model.primary = openai-codex/gpt-5.5 using a ChatGPT-plan OAuth auth profile.
- Start a fresh session and send several turns that involve tool calls (so reasoning items accumulate in history). They succeed.
- Wait for the openai-codex OAuth access token to be refreshed — happens periodically, and on every gateway restart (
resolving authentication…). The refresh rewrites ~/.openclaw/agents/<id>/agent/auth-profiles.json + auth-state.json.
- Send another turn in the same session.
Expected
The turn completes normally. A token refresh should not invalidate an in-flight conversation.
Actual
Every turn after the refresh fails:
[openai-transport] WARN [responses] error provider=openai-codex api=openai-codex-responses
model=gpt-5.5 name=Error status=400 code=invalid_encrypted_content type=invalid_request_error
message=400 The encrypted content gAAA...= could not be verified.
Reason: Encrypted content could not be decrypted or parsed.
[agent/embedded] WARN embedded run agent end
[agent/embedded] WARN embedded run failover decision
[diagnostic] ERROR lane task error: lane=main durationMs=2505
error="FailoverError: LLM request failed: provider rejected the request schema or tool payload."
[model-fallback/decision] WARN model fallback decision
ERROR Embedded agent failed before reply: LLM request failed: provider rejected the request schema or tool payload.
Run trajectory for the failed run:
{ "type": "model.completed",
"data": { "terminalError": "non_deliverable_terminal_turn",
"assistantTexts": [],
"promptCache": { "lastCallUsage": { "input": 0, "output": 0, "total": 0 } } } }
{ "type": "session.ended", "data": { "status": "error",
"terminalError": "non_deliverable_terminal_turn" } }
The session stores "[assistant turn failed before producing content]" (or an empty assistant message) and the user gets nothing back.
Evidence — refresh correlates exactly with the breakage
Session e4a909f8:
| Time (KST) |
Event |
Result |
| 17:05 / 17:10 / 17:12 / 17:15 |
4 turns |
success |
| 17:19:25 |
auth-profiles.json + auth-state.json rewritten (token refresh) |
— |
| 17:20:23 |
next turn |
400 invalid_encrypted_content |
Session bf79fe90:
| Time (KST) |
Event |
Result |
| 16:09 |
first turn |
success |
| 16:29:56 |
gateway restart → resolving authentication… (token re-issued) |
— |
| 16:33 / 16:38 / 17:03 / 17:04 |
every turn |
400 invalid_encrypted_content |
Trivial sessions (e.g. heartbeat) keep working because they don't replay accumulated reasoning items.
Root cause (two distinct issues)
1. Stale encrypted_content reasoning replay.
For openai-codex-responses, OpenClaw replays encrypted_content reasoning items from prior turns. These blobs are cryptographically bound to the OAuth access token that generated them. After a token refresh, all previously-stored encrypted_content is undecryptable, but OpenClaw keeps sending it.
Note: requiresReasoningContentReplay("gpt-5.5") returns false (gpt-5.5 is not in REASONING_CONTENT_REPLAY_MODEL_IDS = kimi-* / mimo-* / deepseek-v4-*), so the transcript policy resolves dropReasoningFromHistory: true. Despite that, the outgoing request still contains stale encrypted_content and is rejected — so either the codex provider path bypasses the dropReasoningFromHistory transcript policy, or dropReasoningFromHistory / shouldPreserveCurrentToolTurnReasoning does not strip encrypted_content thoroughly enough. Maintainers should verify which.
2. No recovery on invalid_encrypted_content.
The 400 code=invalid_encrypted_content is classified as FailoverError: provider rejected the request schema or tool payload and routed to model-fallback. With no fallback model configured, the run hard-fails with zero output. invalid_encrypted_content is a recoverable, well-known Codex error and should not be treated as a schema/tool-payload rejection.
Suggested fix
- On
400 code=invalid_encrypted_content: retry once on the same model after stripping all encrypted_content from reasoning items in the request. Do not route to model fallback.
- And/or: tag stored reasoning items with the credential/token id that produced them; when the active openai-codex token changes, drop reasoning items whose token id no longer matches before building the request.
- And/or: drop/invalidate stored
encrypted_content for affected sessions whenever the openai-codex OAuth token is refreshed.
Impact
Any multi-turn openai-codex (ChatGPT-plan OAuth) session becomes permanently unusable after the first token refresh — which happens roughly hourly and on every gateway restart. The user gets a completely silent failure (empty turn, no error surfaced to the channel).
openai-codex provider: multi-turn sessions permanently break with
400 invalid_encrypted_contentafter an OAuth token refreshSummary
When the agent model is
openai-codex/gpt-5.5authenticated via a ChatGPT-plan OAuth profile, any multi-turn session works for a while and then every subsequent turn fails silently with HTTP400 invalid_encrypted_contentfrom the Codex Responses API.The failure is deterministically triggered by an OAuth access-token refresh. OpenClaw replays
encrypted_contentreasoning items from earlier turns; those blobs are bound to the access token that produced them, so once the token is refreshed (periodic refresh, or re-auth on gateway restart) the Codex backend can no longer decrypt them and rejects the whole request.The user receives no reply at all — the run ends as an empty
non_deliverable_terminal_turn.Environment
2026.5.18(build50a2481, trace gitSha39ee0dd)v25.9.0openai-codex, modelgpt-5.5, apiopenai-codex-responses, harnesspiauth_mode=chatgpt, planpro)localSteps to reproduce
model.primary = openai-codex/gpt-5.5using a ChatGPT-plan OAuth auth profile.resolving authentication…). The refresh rewrites~/.openclaw/agents/<id>/agent/auth-profiles.json+auth-state.json.Expected
The turn completes normally. A token refresh should not invalidate an in-flight conversation.
Actual
Every turn after the refresh fails:
Run trajectory for the failed run:
{ "type": "model.completed", "data": { "terminalError": "non_deliverable_terminal_turn", "assistantTexts": [], "promptCache": { "lastCallUsage": { "input": 0, "output": 0, "total": 0 } } } } { "type": "session.ended", "data": { "status": "error", "terminalError": "non_deliverable_terminal_turn" } }The session stores
"[assistant turn failed before producing content]"(or an empty assistant message) and the user gets nothing back.Evidence — refresh correlates exactly with the breakage
Session
e4a909f8:auth-profiles.json+auth-state.jsonrewritten (token refresh)Session
bf79fe90:resolving authentication…(token re-issued)Trivial sessions (e.g. heartbeat) keep working because they don't replay accumulated reasoning items.
Root cause (two distinct issues)
1. Stale
encrypted_contentreasoning replay.For
openai-codex-responses, OpenClaw replaysencrypted_contentreasoning items from prior turns. These blobs are cryptographically bound to the OAuth access token that generated them. After a token refresh, all previously-storedencrypted_contentis undecryptable, but OpenClaw keeps sending it.Note:
requiresReasoningContentReplay("gpt-5.5")returnsfalse(gpt-5.5 is not inREASONING_CONTENT_REPLAY_MODEL_IDS= kimi-* / mimo-* / deepseek-v4-*), so the transcript policy resolvesdropReasoningFromHistory: true. Despite that, the outgoing request still contains staleencrypted_contentand is rejected — so either the codex provider path bypasses thedropReasoningFromHistorytranscript policy, ordropReasoningFromHistory/shouldPreserveCurrentToolTurnReasoningdoes not stripencrypted_contentthoroughly enough. Maintainers should verify which.2. No recovery on
invalid_encrypted_content.The
400 code=invalid_encrypted_contentis classified asFailoverError: provider rejected the request schema or tool payloadand routed to model-fallback. With no fallback model configured, the run hard-fails with zero output.invalid_encrypted_contentis a recoverable, well-known Codex error and should not be treated as a schema/tool-payload rejection.Suggested fix
400 code=invalid_encrypted_content: retry once on the same model after stripping allencrypted_contentfrom reasoning items in the request. Do not route to model fallback.encrypted_contentfor affected sessions whenever the openai-codex OAuth token is refreshed.Impact
Any multi-turn
openai-codex(ChatGPT-plan OAuth) session becomes permanently unusable after the first token refresh — which happens roughly hourly and on every gateway restart. The user gets a completely silent failure (empty turn, no error surfaced to the channel).