Sub-agent busy-waiting burns a week of quota: 6,932 blocking waits, 23.7% returning empty, in one 11-day session

Open 💬 2 comments Opened Jul 31, 2026 by phyrexia
💡 Likely answer: A maintainer (github-actions[bot], contributor) responded on this thread — see the highlighted reply below.

Plan: Pro. Client: Codex CLI. Period: 2026-07-19 to 2026-07-30.

This is not a quota-accounting report. The accounting may well be correct — the
problem is what the client spends the quota on. In my case a single
long-lived session consumed 71% of my account's total token usage for the
period, and a large share of that was spent waiting for sub-agents rather than
doing work.

All figures below are counted from my own local rollout file.

Shape of the session

One session stayed open for 11 days and produced a single 475 MB rollout with
125,799 events. It was a multi-agent orchestration: 2,562 sub-agent activity
events, plus an agent-to-agent message tree.

The supervising agent polls its sub-agents with a blocking wait call. Those
calls look like this:

{"session_id":<id>,"chars":"","yield_time_ms":30000,"max_output_tokens":10000}

I counted 6,932 of these wait calls. 1,644 of them (23.7%) came back with
chars empty
— the wait elapsed and the sub-agent had produced nothing.

Declared wait time across all of them totals 35.7 hours. The most common
single form is a 30-second wait, used 3,127 times.

Why this is expensive

Each wait is a full billed turn. The model re-reads the accumulated session
context and receives an empty string. Because the context grows with the
session, the cost of one empty wait on day 11 is far higher than on day 1 — the
same non-event gets more expensive the longer the session lives.

The escalation is visible in the daily counts:

day 1   (Jul 19)    124 waits,     0 empty
day 5   (Jul 23)    277 waits,    23 empty
day 8   (Jul 26)    815 waits,   136 empty
day 11  (Jul 29)    768 waits,   310 empty
day 12  (Jul 30)  1,748 waits,   523 empty

On the last day that session alone accounted for the bulk of my consumption,
and my client logged 379 usage_limit_exceeded errors that day. For scale: I
measured my weekly window as holding roughly 1.9-3.7B tokens, so a single day
of this is enough to exhaust one.

The session recorded only 8 errors in 11 days. Nothing failed, nothing
retried, nothing surfaced a warning — which is exactly why it ran for 11 days
without anyone noticing.

What I think the defect is

Three conditions combine:

  1. a session with no lifetime bound, kept alive for days;
  2. sub-agent coordination implemented as busy-waiting — the supervisor

polls with a blocking call instead of being notified on completion;

  1. sub-agents that routinely take longer than yield_time_ms, which guarantees

the poll returns empty and will be repeated.

A poll that returns nothing should not cost a full context re-read. Either the
wait should not be modelled as a billed turn, or the supervisor should be woken
on completion rather than polling.

What would help, in the client

  • Surface a warning when one session dominates account consumption, or when

priced calls repeat at a fixed interval — both are detectable client-side
from the rollout the CLI already writes.

  • Expose empty-return waits in /usage so the cost of coordination is visible

and separable from the cost of work.

  • Bound session lifetime, or at least warn when a session has been open for

days and its per-turn context has grown past some threshold.

Related

#36313 and #36307 cover the weekly-window behaviour. This is a separate
problem: even with correct quota accounting, this pattern burns a week of
allowance on waiting.

Happy to share the extraction method — it only reads fields the CLI already
writes locally, and needs no access to my transcripts.

View original on GitHub ↗

2 Comments

github-actions[bot] contributor · 27 days ago

Potential duplicates detected. Please review them and close your issue if it is a duplicate.

  • #35259

Powered by Codex Action

zac343 · 27 days ago

I traced this against the current rollout structure and shipped support for this pattern in the local auditor.

One precision point matters for the extraction:

  • write_stdin.arguments.chars is stdin sent into the running process. An empty value identifies a poll, but does not by itself prove that the poll returned no output.
  • I correlate the function call with its function_call_output by call_id, and call it empty only when the recognized tool wrapper has no payload after Output:.
  • I then associate completed blocking waits with the next event_msg.token_count.info.last_token_usage. That gives recorded token usage for the model turn that issued the wait without claiming a separate backend "wait fee".
  • Summed yield_time_ms is reported as requested timeout ceilings, not measured elapsed time.

The browser-only parser now handles both write_stdin and wait, streams large Codex rollouts locally, and sends only anonymized aggregates if a report is requested:

https://mailcheck.agentcartai.com/tools/agent-cost-auditor/?platform=codex&utm_source=github&utm_medium=issue-reply&utm_campaign=codex-blocking-wait-36396

For comparing extraction methods, the useful questions are whether each of the 6,932 calls has a matching tool output and an adjacent last_token_usage, and whether the 1,644 empties were classified from the output payload rather than the input chars field. Please do not attach the raw rollout or prompts publicly.

The local diagnosis is free. The optional 1 USDC Base checkout is only for a downloadable support-ready evidence pack.