Desktop wait/status orchestration re-meters full cached context every 10-30s; stale 'running' subagents keep it looping (90% of weekly Pro in 15.5h)
Summary
Long-running Codex Desktop tasks that orchestrate subagents and background commands consume extreme weekly usage while effectively idle. The driver is that every small wait/status result triggers a model turn that re-meters the task's entire accumulated context (~140k tokens at 97–98% cached in our case), and Desktop's orchestration issues these waits on 10–30s timeouts against work that takes minutes to hours. A newly activated Pro account went from 0% to 90% of its weekly limit in ~15.5 hours this way; the account usage API reported ~290M tokens for that single day (Aug 5).
Related: #4764 (large cached contexts consuming limits). This report adds the Desktop wait/status orchestration angle plus a subagent lifecycle defect that keeps the pattern running.
Environment
- Codex Desktop (app-server
0.147.0-alpha.1.2), macOS 15 (Darwin 24.6.0) - Model
gpt-5.6-sol, reasoningxhigh/max, multi-agent + code-mode enabled
Measurements (local session rollouts, fork/replay events deduplicated)
Over Aug 4–6:
- 75% of all model-visible tool calls were
wait/wait_agent/list_agents(8,744 of 11,002). Observed timeout arguments:waitmostlyyield_time_ms10,000–30,000;wait_agentmostlytimeout_ms30,000–60,000. - 83% of
wait_agentcalls returnedtimed_out: true(1,765 of 2,122) — no-op turns whose only effect is re-metering the full cached context. - Average input per turn ~137–141k tokens, 97.4–98.1% cached, sustained across 15–25k turns/day (2.4–3.6B input tokens/day processed on one workstation).
- One long-lived thread issued 3,834 wait-family calls over its lifetime.
- Tool outputs returned through
waittotaled 28.2MB over three days (60% of all tool-result bytes), much of it unprojected upstream API JSON that then persists in context and is re-metered on every subsequent turn.
Subagent lifecycle defect (keeps the loop alive and inflates rosters)
- Completed subagents remain
running/"Working" in the roster and UI for days. In our local Codex state, 1,154 child-task relationships are marked open; 1,071 have no update for >24h; 815 for >72h; in a 200-item sample, 184 already contained a final answer. - Direct example: a subagent whose rollout file went silent at 06:42 still reported
agent_status: "running"inlist_agentsat 07:48. - Thread forks/continuations rewrite the full rollout history in bulk (e.g., an 86MB rollout written in under a minute, 8,879 of 8,908 token events being replayed history) and re-import the parent roster including stale "running" agents — so ghost agents survive across generations, and parents keep polling agents that will never change state.
list_agentsembeds each completed agent's entire final message in the roster payload, so every roster check re-injects those reports into context.
Impact
- Fresh Pro weekly limit 0% → 90% in ~15.5 hours, dominated by two long-lived tasks; ~290M account-reported tokens in one day.
- The workload pattern (agentic delivery with verifier subagents and CI waits) is exactly what Desktop multi-agent encourages, so this scales with adoption of the feature.
Suggested directions
- Server-side or client-side long-wait primitive: a bounded wait on subagents/commands should not cost one full-context model turn per 10–30s slice, and a
timed_outno-change result ideally should not trigger a model turn at all. - Fix child-task lifecycle: transition completed subagents out of
runningwithout requiring the parent to reap them; do not re-import stale rosters into forks/continuations. - Return roster/status summaries (name, state, timestamps) instead of embedding full final messages in
list_agents. - Clarify how cached context in orchestration turns is weighted against plan limits (follow-up to #4764) — the current behavior makes long-lived tasks pay near-max context for idle polling.
Happy to provide session IDs, rollout excerpts, timestamps, and the affected account privately through support (ticket references this issue).
cc @tibo-openai
4 Comments
Follow-up measurement from one Windows Codex Desktop installation (anonymized; no session text, IDs, paths, account data, or rollout excerpts).
Method:
token_countcounters.functions.wait >= 180000 ms, with 300000 ms preferred when no intermediate output is needed.Observed local counts:
functions.waitcalls; 158 were <180 s and 82 were <30 s. Local counters totaled 78.2M tokens (74.8M cached input).functions.waitcalls; 80 were <180 s and 40 were <30 s. All 80 short waits came from one session; the other 9/10 sessions had zero shortfunctions.waitcalls. There were also 12wait_agentand 6list_agentscalls. Local counters totaled 863.5M tokens (834.7M cached input).Interpretation limits:
functions.waitpattern in most new sessions, but one post-policy session still looped at 1–30 s. That makes an enforced client/server no-change long-wait primitive important: guidance alone is not a reliable guardrail.This independently supports the request that no-change timeout/status results should avoid waking a full model turn, and that
wait_agent/list_agentsneed the same treatment as code-cell waits.I can reproduce a very similar pattern on Codex app-server 0.148.0-alpha.9 with GPT-5.6 Sol xhigh and multi-agent.
In my case, a single multi-agent session exhausted essentially 100% of my weekly Codex allowance in about two hours.
Observed timeline:
• ~0% weekly usage at 19:37 CEST
• ~50% at 20:56
• ~80% at 21:19
• ~95% at 21:34
• 100% at approximately 21:40
Parent thread:
01a0203f-10c5-7431-998e-c2643c4220ab
The parent spawned six subagents across two waves. All inherited GPT-5.6 Sol + xhigh.
After deduplicating forked/replayed token_count history, my local rollout analysis indicates approximately:
• 174M total processed tokens
• 169M cached input tokens
• ~97–98% cached input on many turns
• ~135k average input tokens per parent model turn
• 8 of 9 observed wait_agent calls timed out
I can also see cases where a wait_agent timeout is followed by another model turn processing 100k–190k+ input tokens, almost entirely cached.
This looks extremely similar to the re-metering behavior described in this issue.
I have retained the parent and all six subagent rollout JSONLs and can provide sanitized excerpts if useful.
Independent data point from a Linux Codex CLI audit:
I observed the same qualitative pattern, although at a much smaller scale than reported here.
Across one audited workflow:
The larger session accumulated 55.9M total MAIN input tokens, with repeated context processing becoming a major component of usage.
One additional observation: cached input changes the economics, but does not remove the architectural amplification. A semantically empty status check can still cause a very large existing context to be processed again.
So I would strongly support exposing telemetry for:
Without separating those three, normal agent work and orchestration overhead are difficult to distinguish from account-level usage numbers.
Same pattern on plain CLI, and it removes the two variables your report is most likely to be dismissed on: no Desktop, no subagents, single session.
Codex CLI 0.149.0,
codex execinvoked non-interactively by an orchestrator. One agent, nowait_agent, no roster, no fork/replay. The polling iswrite_stdinagainst aunified_execsession. Your per-turn profile reproduces almost exactly:| | yours | ours |
|---|---|---|
| input/turn | ~137–141k | ~137k avg |
| cached | 97.4–98.1% | 98.3% |
| wait-family share | 75% of tool calls | 48% of turns (84 of 175) |
Session totals (175 turns): input 23,957,294 · cached 23,542,272 · new input 415,022 · output 52,694 · input:output 454:1.
Splitting by turn kind isolates the cost:
| | poll turns | work turns |
|---|---|---|
| turns | 84 | 90 |
| context re-sent | 13,507,179 | 10,450,115 |
| new input | 179,819 | 235,203 |
| output | 6,151 | 46,543 |
| avg output/turn | 73 | 517 |
The polls re-metered more context than all the real work combined, for 12% of the output. Cache hit rate on the poll turns alone: 98.7% (13,327,360 of 13,507,179) — so this is your "re-meters full cached context" with the cache working perfectly, and it still cost 13.5M.
The mechanism, which I think your report is missing and which explains why this is structural rather than a Desktop bug. Checked against
102ae5e2e6ff:core/src/unified_exec/mod.rs:72—pub(crate) const MAX_YIELD_TIME_MS: u64 = 30_000;core/src/unified_exec/mod.rs:213—yield_time_ms.clamp(MIN_YIELD_TIME_MS, MAX_YIELD_TIME_MS)core/src/unified_exec/process_manager.rs:575—clamp_yield_timeis applied where the comment reads "For the initial exec_command call"So no single tool call can observe a process for more than 30s, including the first one, and it is a compile-time constant with no config override (
background_terminal_max_timeoutinconfig_toml.rs:308bounds how long the process may live, not how long a call may block). Any wait longer than 30s is therefore structurally N re-metered turns, where N = duration/30s. #38495 has the protocol-level framing of the same thing: resumption is pull, not push.That matters for triage: your stale-subagent defect is a real amplifier — it manufactures waits that never end — but it is not the cause. Remove Desktop, remove subagents, remove stale rosters, and a single
codex execwaiting on an ordinary 21.6-minute CI pipeline still burns 13.5M of metered context to learn nothing.Our wait was legitimate, incidentally: the agent had finished, committed, pushed and opened a PR, and was waiting on its own pipeline whose jobs run sequentially with multi-minute gaps. Nothing was hung and it had no signal telling it to stop.
---
Cross-linking, since these threads don't reference each other: #38495 is the mechanism-level report (pull-not-push, no completion push, no timer) and proposes escalating server-side yield + completion push as fixes. It leaves the caching question open — its author suspected a cache miss inflated their numbers. Your data answers it: the account usage API returning ~290M for one day at 97–98% cache is the evidence that cached context is metered regardless. Combined, the two give a conclusion neither has alone: a cache fix would not help, and the yield/push fixes pay for themselves in quota even when caching is perfect.