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)

Open 💬 4 comments Opened Aug 6, 2026 by swordfish444

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, reasoning xhigh/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: wait mostly yield_time_ms 10,000–30,000; wait_agent mostly timeout_ms 30,000–60,000.
  • 83% of wait_agent calls returned timed_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 wait totaled 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" in list_agents at 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_agents embeds 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

  1. 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_out no-change result ideally should not trigger a model turn at all.
  2. Fix child-task lifecycle: transition completed subagents out of running without requiring the parent to reap them; do not re-import stale rosters into forks/continuations.
  3. Return roster/status summaries (name, state, timestamps) instead of embedding full final messages in list_agents.
  4. 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

View original on GitHub ↗

4 Comments

John-Lin98 · 15 days ago

Follow-up measurement from one Windows Codex Desktop installation (anonymized; no session text, IDs, paths, account data, or rollout excerpts).

Method:

  • Aggregated only local JSONL tool-event metadata, wait arguments, and the local token_count counters.
  • Excluded the active session.
  • Split sessions at the time a global long-wait instruction was added: functions.wait >= 180000 ms, with 300000 ms preferred when no intermediate output is needed.

Observed local counts:

  • Before the policy: 2 sessions, 161 functions.wait calls; 158 were <180 s and 82 were <30 s. Local counters totaled 78.2M tokens (74.8M cached input).
  • After the policy: 10 sessions, 262 functions.wait calls; 80 were <180 s and 40 were <30 s. All 80 short waits came from one session; the other 9/10 sessions had zero short functions.wait calls. There were also 12 wait_agent and 6 list_agents calls. Local counters totaled 863.5M tokens (834.7M cached input).

Interpretation limits:

  • The before/after workloads, session lengths, and contexts are not comparable, so this does not estimate token savings or prove billing attribution.
  • These are client-side local counters, not server-side telemetry.
  • The instruction-level workaround substantially changed the observed functions.wait pattern 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_agents need the same treatment as code-cell waits.

GorangN · 7 days ago

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.

lunara69-ctrl · 4 days ago

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:

  • 58 no-new-information WAIT/POLL model continuations
  • 6,787,563 total input tokens attributable to those continuations
  • ~117,027 input tokens per WAIT/POLL continuation on average

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:

  1. logical wait operations,
  2. parent-model resumptions caused by them,
  3. input/cached-input processed per resumption.

Without separating those three, normal agent work and orchestration overhead are difficult to distinguish from account-level usage numbers.

jmtt89 · 1 day ago

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 exec invoked non-interactively by an orchestrator. One agent, no wait_agent, no roster, no fork/replay. The polling is write_stdin against a unified_exec session. 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:72pub(crate) const MAX_YIELD_TIME_MS: u64 = 30_000;
  • core/src/unified_exec/mod.rs:213yield_time_ms.clamp(MIN_YIELD_TIME_MS, MAX_YIELD_TIME_MS)
  • core/src/unified_exec/process_manager.rs:575clamp_yield_time is 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_timeout in config_toml.rs:308 bounds 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 exec waiting 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.