Summary
For openai-codex provider with gpt-5.5 over the openai-codex-responses API, the codex app-server can return a turn where the entire output is hidden reasoning tokens with assistantTexts=[] and zero visible content. Gateway accepts the turn as a successful completion (no failover triggered) and the channel transport delivers nothing — Discord/Telegram clients eventually show "The application did not respond."
The downstream agent and the user receive no useful content despite the model burning real tokens. This is a silent failure mode: decision=succeeded masks a fully empty turn.
Environment
- OpenClaw
2026.5.20 (e510042) — npm install at ~/.local/lib/node_modules/openclaw
- Node 25.8.1, macOS 25.3.0 (arm64)
- Provider:
openai-codex / model gpt-5.5 / modelApi: openai-codex-responses
- Codex app-server: stdio
- Channel: Discord direct (channel
1499764682443980892)
- Auth: openai-codex OAuth profile
gabrielexito@gmail.com
Reproduction
- Configure an OpenClaw agent that uses
openai-codex provider with gpt-5.5 (codex app-server transport).
- Have an active Discord (or any messaging) channel session for the agent.
- Send a short prompt that the model will answer with reasoning tokens (e.g. "u there partner ??" after the session has prior history).
- Periodically the codex app-server returns a turn where
output tokens are non-zero but assistantTexts is [] and messagesSnapshot contains only the user message.
In our incident (2026-05-22), every other turn on this session succeeded; this is intermittent, not deterministic.
Error / trajectory evidence
Trajectory model.completed event for the empty turn (sanitized), ~/.openclaw/agents/chatgpt/sessions/3c0b493e-…trajectory.jsonl:
{
"type": "model.completed",
"ts": "2026-05-22T10:18:35.515Z",
"runId": "d10373ab-0a95-40a7-ae7f-7459c745ea61",
"provider": "openai-codex",
"modelId": "gpt-5.5",
"modelApi": "openai-codex-responses",
"data": {
"timedOut": false,
"yieldDetected": false,
"aborted": false,
"promptError": null,
"usage": {"input": 24794, "output": 111, "cacheRead": 4608, "total": 29513},
"assistantTexts": [],
"messagesSnapshot": [{"role": "user", "content": "u there partner ??..."}]
}
}
The preceding (runId=5a61ca06) and following turns on the same session have non-empty assistantTexts. No embedded_run_failover_decision event was emitted for this run, no model-fallback chain was attempted — Gateway treated the turn as completed successfully.
Root cause
In src/agents/pi-embedded-runner/run/incomplete-turn.ts, the empty-response retry predicate is gated through isEmptyResponseAssistantTurn():
// src/agents/pi-embedded-runner/run/incomplete-turn.ts:412
function isEmptyResponseAssistantTurn(params: {
payloadCount: number;
attempt: Pick<IncompleteTurnAttempt, "assistantTexts" | "currentAttemptAssistant" | "lastAssistant">;
}): boolean {
if (params.payloadCount !== 0) {
return false;
}
if (joinAssistantTexts(params.attempt.assistantTexts).length > 0) {
return false;
}
const assistant = params.attempt.currentAttemptAssistant ?? params.attempt.lastAssistant;
if (!assistant) {
return true;
}
if (assistant.stopReason === "error") {
return false;
}
if (
isIncompleteTerminalAssistantTurn({ hasAssistantVisibleText: false, lastAssistant: assistant }) ||
isReasoningOnlyAssistantTurn(assistant) // ← problem
) {
return false;
}
return true;
}
The predicate explicitly returns false when isReasoningOnlyAssistantTurn(assistant) is true, meaning a reasoning-only turn goes to resolveReasoningOnlyRetryInstruction instead of resolveEmptyResponseRetryInstruction. For the openai-codex-responses payload shape, the reasoning-only retry path doesn't appear to trigger either — messagesSnapshot has no assistant message at all, and the codex app-server's response carries hidden reasoning under usage but exposes nothing on the assistant role for isReasoningOnlyAssistantTurn to detect. The net effect: the turn passes through all empty/reasoning guards and is treated as success.
embedded_run_failover_decision (src/agents/pi-embedded-runner/run/failover-observation.ts:70) is therefore never fired, and the model-fallback chain configured for the agent (which works fine for HTTP 408/timeouts — proven by the ops/gpt-5.5 → claude-opus-4-7 fallback at 15:31:17 ICT in the same incident window) does not activate.
Suggested fix
Add an emptyAssistantTextWithReasoningTokens failover predicate, triggered when:
payloadCount === 0
joinAssistantTexts(attempt.assistantTexts).length === 0
usage.output > 0 (the model spent tokens but emitted nothing visible)
messagesSnapshot has no assistant message
- transport is
openai-codex-responses (scope to the affected provider)
Either trigger the empty-response retry path (emptyResponseRetryInstruction), or classify the turn as FailoverReason="empty_response" and let the standard model-fallback chain decide.
// rough shape
function isCodexEmptyVisibleTurnDespiteOutputTokens(params: {
modelApi?: string;
attempt: IncompleteTurnAttempt;
usage?: { output?: number };
}): boolean {
return (
params.modelApi === "openai-codex-responses" &&
(params.usage?.output ?? 0) > 0 &&
joinAssistantTexts(params.attempt.assistantTexts).length === 0 &&
!params.attempt.messagesSnapshot?.some((m) => m.role === "assistant")
);
}
Workaround
Switch the agent's primary model off gpt-5.5 (the codex app-server path). Config-level swap in ~/.openclaw/agents/<id>/agent.json to e.g. claude-sonnet-4-6 avoids the failure mode entirely.
Severity
P2 — silent message loss on a production-facing channel. Not a crash, but the user sees no response and no error, and the model-fallback chain configured by the operator is bypassed. Different from #85192 (DeepSeek V4 unsigned thinking blocks) and #76048 (ZAI GLM-5 reasoning_content routing) — those are distinct providers; this is openai-codex-responses with non-empty usage.output and messagesSnapshot missing an assistant entry entirely.
Related
#78384 — assistant can reply to stale user intent after reasoning-only/tool retry (overlaps with the reasoning-only detection path)
#85192 — DeepSeek V4 unsigned thinking blocks miss reasoning-only retry (same class, different provider)
#80918 — silent send miss: incomplete-turn classifier discards stopReason=stop final after update_plan (adjacent)
#84076 — Codex app-server stalls after item/completed (different symptom; this issue is a clean model.completed with empty content, not a stall)
Summary
For
openai-codexprovider withgpt-5.5over theopenai-codex-responsesAPI, the codex app-server can return a turn where the entire output is hidden reasoning tokens withassistantTexts=[]and zero visible content. Gateway accepts the turn as a successful completion (no failover triggered) and the channel transport delivers nothing — Discord/Telegram clients eventually show "The application did not respond."The downstream agent and the user receive no useful content despite the model burning real tokens. This is a silent failure mode:
decision=succeededmasks a fully empty turn.Environment
2026.5.20(e510042) — npm install at~/.local/lib/node_modules/openclawopenai-codex/ modelgpt-5.5/modelApi: openai-codex-responses1499764682443980892)gabrielexito@gmail.comReproduction
openai-codexprovider withgpt-5.5(codex app-server transport).outputtokens are non-zero butassistantTextsis[]andmessagesSnapshotcontains only the user message.In our incident (2026-05-22), every other turn on this session succeeded; this is intermittent, not deterministic.
Error / trajectory evidence
Trajectory
model.completedevent for the empty turn (sanitized),~/.openclaw/agents/chatgpt/sessions/3c0b493e-…trajectory.jsonl:{ "type": "model.completed", "ts": "2026-05-22T10:18:35.515Z", "runId": "d10373ab-0a95-40a7-ae7f-7459c745ea61", "provider": "openai-codex", "modelId": "gpt-5.5", "modelApi": "openai-codex-responses", "data": { "timedOut": false, "yieldDetected": false, "aborted": false, "promptError": null, "usage": {"input": 24794, "output": 111, "cacheRead": 4608, "total": 29513}, "assistantTexts": [], "messagesSnapshot": [{"role": "user", "content": "u there partner ??..."}] } }The preceding (
runId=5a61ca06) and following turns on the same session have non-emptyassistantTexts. Noembedded_run_failover_decisionevent was emitted for this run, no model-fallback chain was attempted — Gateway treated the turn as completed successfully.Root cause
In
src/agents/pi-embedded-runner/run/incomplete-turn.ts, the empty-response retry predicate is gated throughisEmptyResponseAssistantTurn():The predicate explicitly returns
falsewhenisReasoningOnlyAssistantTurn(assistant)is true, meaning a reasoning-only turn goes toresolveReasoningOnlyRetryInstructioninstead ofresolveEmptyResponseRetryInstruction. For theopenai-codex-responsespayload shape, the reasoning-only retry path doesn't appear to trigger either —messagesSnapshothas no assistant message at all, and the codex app-server's response carries hidden reasoning under usage but exposes nothing on the assistant role forisReasoningOnlyAssistantTurnto detect. The net effect: the turn passes through all empty/reasoning guards and is treated as success.embedded_run_failover_decision(src/agents/pi-embedded-runner/run/failover-observation.ts:70) is therefore never fired, and the model-fallback chain configured for the agent (which works fine for HTTP 408/timeouts — proven by the ops/gpt-5.5 → claude-opus-4-7 fallback at 15:31:17 ICT in the same incident window) does not activate.Suggested fix
Add an
emptyAssistantTextWithReasoningTokensfailover predicate, triggered when:payloadCount === 0joinAssistantTexts(attempt.assistantTexts).length === 0usage.output > 0(the model spent tokens but emitted nothing visible)messagesSnapshothas no assistant messageopenai-codex-responses(scope to the affected provider)Either trigger the empty-response retry path (
emptyResponseRetryInstruction), or classify the turn asFailoverReason="empty_response"and let the standard model-fallback chain decide.Workaround
Switch the agent's primary model off
gpt-5.5(the codex app-server path). Config-level swap in~/.openclaw/agents/<id>/agent.jsonto e.g.claude-sonnet-4-6avoids the failure mode entirely.Severity
P2 — silent message loss on a production-facing channel. Not a crash, but the user sees no response and no error, and the model-fallback chain configured by the operator is bypassed. Different from
#85192(DeepSeek V4 unsigned thinking blocks) and#76048(ZAI GLM-5 reasoning_content routing) — those are distinct providers; this is openai-codex-responses with non-emptyusage.outputandmessagesSnapshotmissing an assistant entry entirely.Related
#78384— assistant can reply to stale user intent after reasoning-only/tool retry (overlaps with the reasoning-only detection path)#85192— DeepSeek V4 unsigned thinking blocks miss reasoning-only retry (same class, different provider)#80918— silent send miss: incomplete-turn classifier discards stopReason=stop final after update_plan (adjacent)#84076— Codex app-server stalls afteritem/completed(different symptom; this issue is a cleanmodel.completedwith empty content, not a stall)