What Problem This Solves
A codex app-server turn can reach a state where all work is finished but the turn is never marked complete. It then sits idle for the full 300 s completion timeout, the shared client is retired, and the caller retries the entire turn from scratch. Repeated across retries this silently multiplies cost and makes the agent appear frozen mid-tool-call to the end user.
I can prove the symptom precisely. I cannot prove which side is at fault from my logs alone, so I'm filing the same report on both openai/codex and openclaw/openclaw (companion issue: openclaw/openclaw#115445) with an explicit blame-weighting and full disclosure, rather than guessing and wasting a maintainer's time.
Environment
|
|
| OpenClaw |
2026.7.1 (2d2ddc4) |
| Codex harness plugin |
@openclaw/codex 2026.7.1 |
| codex app-server |
codex-cli 0.144.3 (the copy bundled inside the plugin, not the global CLI) |
| Transport |
codex app-server --listen stdio:// |
| Model |
gpt-5.6-sol, model_context_window: 258400 |
| Multi-agent |
multi_agent_version: "v2" (native codex subagents in use) |
| Host |
macOS 15.7.3, x86_64, node v25.9.0 |
Evidence
Two log records, one millisecond apart, from subsystem agent/embedded:
{
"threadId": "019fa876-d4ff-7fa0-8d45-fe3e773c54c0",
"turnId": "019fa8ba-ce8d-7080-9fb9-de2e49f5e1c8",
"idleMs": 300000,
"timeoutMs": 300000,
"lastActivityReason": "notification:item/completed",
"lastNotificationMethod": "item/completed",
"activeAppServerTurnRequests": 0,
"activeTurnItemCount": 0
}
codex app-server turn idle timed out waiting for completion
{
"threadId": "019fa876-d4ff-7fa0-8d45-fe3e773c54c0",
"turnId": "019fa8ba-ce8d-7080-9fb9-de2e49f5e1c8",
"reason": "turn_completion_idle_timeout",
"detachedSharedClient": true,
"closedClient": false,
"activeSharedClientLeases": 1
}
codex app-server client retired after timed-out turn
The key point: the last thing received was item/completed, and at that moment there were zero outstanding app-server turn requests and zero active turn items. By every counter available, the work was done. The turn still never closed.
Expected vs observed
- Expected: after
item/completed with no outstanding requests or items, the turn is finalised and the caller proceeds.
- Observed: the turn remains open for the full 300 s idle timeout, then the client is retired and the whole turn is retried.
Impact
Measured on a real workload (an iterate-on-tests session: npm run lint, node --test, pytest):
- A single session logged 526 model calls, many returning 45–234 output tokens against ~209 K of context each.
- Cumulative input across successive retried sessions escalated: 9.5M → 10.7M → 14.6M → 28.7M → 36.1M → 34.0M → 38.5M, because each retry re-ran the same commands against a larger accumulated transcript.
- Across 12 rollouts: 182 M input / 177 M cached (97.4 % hit) / 436 K output.
Cache absorbs most of it, but the work is genuinely redone each cycle, and to the user the agent simply appears frozen part-way through a tool sequence.
Blame weighting — full disclosure
I have only OpenClaw's side of the stdio channel. I did not capture the wire traffic, so the following is reasoning, not proof:
Possibly OpenClaw (~55%)
- The idle timeout that fires is OpenClaw's own.
detachedSharedClient: true together with activeSharedClientLeases: 1 is OpenClaw's own bookkeeping. A lease still held while the client is detached looks like a leak on this side, and is exactly the kind of state that would stop a turn being considered finished.
- If OpenClaw is waiting for a terminal event that this codex version doesn't emit in this path, that is a compatibility assumption on OpenClaw's side.
Possibly codex (~45%)
- If the app-server is expected to emit a distinct terminal turn event after
item/completed and does not in this path, the completion signal never arrives and no amount of caller-side handling fixes it.
- The failure showed up in a
multi_agent_version: v2 session with native subagents active, so a subagent-lifecycle path may be involved in the parent turn never finalising.
What would settle it definitively: a capture of the JSON-RPC traffic on the stdio channel across one hang. If codex emits a completion that OpenClaw ignores → OpenClaw. If codex goes silent after item/completed → codex. I'm happy to produce that capture if a maintainer tells me which side to instrument, but it requires deliberately reproducing the hang (this install has since moved to a different harness).
Reproduction
I do not have a minimal reproduction, and I want to be straight about that. Conditions under which it occurred:
- Long agentic coding turn (5–20 minutes), heavy
bash tool use (358 invocations, 50 distinct), apply_patch, native codex subagents spawned.
- Occurred repeatedly across a single afternoon, surviving
/reset, gateway restart and a full cold start with a verified-empty process table.
Workaround
Stop the gateway, kill the app-server, start clean, and begin a fresh session. This clears the state but does not prevent recurrence.
Related — possibly the same subsystem
Same install, same day, likely adjacent: an orphaned codex subagent rollout produced this warning every 5–15 seconds indefinitely:
Codex native subagent transcript parent did not match monitor state
child thread : 019fa7e0-b299… (constant)
transcript parent: 019fa7df-21a2… (constant)
expected parent : 019fa7f4-7739… → 019fa831-86a3… → 019fa859-8429… (new one per gateway generation)
One dead subagent (its rollout last written 4 hours earlier) was re-reconciled against whatever thread was currently live, forever. It survived /reset, a gateway restart, and a full cold start, because the state is on disk in codex-home/sessions/**/rollout-*.jsonl — and codex never prunes or renames rollouts (1263 files, 2.7 GB, all plain .jsonl). Renaming the orphan off the *.jsonl glob stopped it immediately, which suggests the monitor discovers these by scanning rollouts.
Disclosure: this report was investigated and written with AI assistance (Claude), from logs on a live production install. Session contents and configuration have been deliberately excluded; only opaque identifiers, counters and version strings are included.
What Problem This Solves
A codex app-server turn can reach a state where all work is finished but the turn is never marked complete. It then sits idle for the full 300 s completion timeout, the shared client is retired, and the caller retries the entire turn from scratch. Repeated across retries this silently multiplies cost and makes the agent appear frozen mid-tool-call to the end user.
I can prove the symptom precisely. I cannot prove which side is at fault from my logs alone, so I'm filing the same report on both
openai/codexandopenclaw/openclaw(companion issue: openclaw/openclaw#115445) with an explicit blame-weighting and full disclosure, rather than guessing and wasting a maintainer's time.Environment
2026.7.1(2d2ddc4)@openclaw/codex2026.7.1codex-cli 0.144.3(the copy bundled inside the plugin, not the global CLI)codex app-server --listen stdio://gpt-5.6-sol,model_context_window: 258400multi_agent_version: "v2"(native codex subagents in use)Evidence
Two log records, one millisecond apart, from subsystem
agent/embedded:{ "threadId": "019fa876-d4ff-7fa0-8d45-fe3e773c54c0", "turnId": "019fa8ba-ce8d-7080-9fb9-de2e49f5e1c8", "idleMs": 300000, "timeoutMs": 300000, "lastActivityReason": "notification:item/completed", "lastNotificationMethod": "item/completed", "activeAppServerTurnRequests": 0, "activeTurnItemCount": 0 }{ "threadId": "019fa876-d4ff-7fa0-8d45-fe3e773c54c0", "turnId": "019fa8ba-ce8d-7080-9fb9-de2e49f5e1c8", "reason": "turn_completion_idle_timeout", "detachedSharedClient": true, "closedClient": false, "activeSharedClientLeases": 1 }The key point: the last thing received was
item/completed, and at that moment there were zero outstanding app-server turn requests and zero active turn items. By every counter available, the work was done. The turn still never closed.Expected vs observed
item/completedwith no outstanding requests or items, the turn is finalised and the caller proceeds.Impact
Measured on a real workload (an iterate-on-tests session:
npm run lint,node --test,pytest):Cache absorbs most of it, but the work is genuinely redone each cycle, and to the user the agent simply appears frozen part-way through a tool sequence.
Blame weighting — full disclosure
I have only OpenClaw's side of the stdio channel. I did not capture the wire traffic, so the following is reasoning, not proof:
Possibly OpenClaw (~55%)
detachedSharedClient: truetogether withactiveSharedClientLeases: 1is OpenClaw's own bookkeeping. A lease still held while the client is detached looks like a leak on this side, and is exactly the kind of state that would stop a turn being considered finished.Possibly codex (~45%)
item/completedand does not in this path, the completion signal never arrives and no amount of caller-side handling fixes it.multi_agent_version: v2session with native subagents active, so a subagent-lifecycle path may be involved in the parent turn never finalising.What would settle it definitively: a capture of the JSON-RPC traffic on the stdio channel across one hang. If codex emits a completion that OpenClaw ignores → OpenClaw. If codex goes silent after
item/completed→ codex. I'm happy to produce that capture if a maintainer tells me which side to instrument, but it requires deliberately reproducing the hang (this install has since moved to a different harness).Reproduction
I do not have a minimal reproduction, and I want to be straight about that. Conditions under which it occurred:
bashtool use (358 invocations, 50 distinct),apply_patch, native codex subagents spawned./reset, gateway restart and a full cold start with a verified-empty process table.Workaround
Stop the gateway, kill the app-server, start clean, and begin a fresh session. This clears the state but does not prevent recurrence.
Related — possibly the same subsystem
Same install, same day, likely adjacent: an orphaned codex subagent rollout produced this warning every 5–15 seconds indefinitely:
One dead subagent (its rollout last written 4 hours earlier) was re-reconciled against whatever thread was currently live, forever. It survived
/reset, a gateway restart, and a full cold start, because the state is on disk incodex-home/sessions/**/rollout-*.jsonl— and codex never prunes or renames rollouts (1263 files, 2.7 GB, all plain.jsonl). Renaming the orphan off the*.jsonlglob stopped it immediately, which suggests the monitor discovers these by scanning rollouts.Disclosure: this report was investigated and written with AI assistance (Claude), from logs on a live production install. Session contents and configuration have been deliberately excluded; only opaque identifiers, counters and version strings are included.