Codex subagents drain full week quota overnight - usage counting broken
What version of Codex CLI is running?
0.145.0
What subscription do you have?
Pro 20x
Which model were you using?
gpt-5.6-sol
What platform is your computer?
MacOS 26.3.1 (Darwin 25.3.0, arm64)
What terminal emulator and version are you using (if applicable)?
_No response_
Codex doctor report
What issue are you seeing?
My entire Pro plan usage quota was drained to 0% overnight on 2026-07-26, correlated specifically with heavy use of the MultiAgent V2 subagent feature (multi_agent_version: "v2"). This does not happen on days when I don't use subagents.
This happened across two independent projects/workspaces on the same machine that day. In both cases I instructed the top-level orchestrator agent to delegate work to subagents for actual task execution. I was using reasoning_effort: xhigh, not any higher/"ultra"-tier workflow preset. Despite that, both workspaces independently produced fork trees up to 4 levels deep (a subagent's subagent's subagent's subagent), which was not something I asked for. As detailed in problem N3 below, this turns out not to be a configuration oversight on my part — there is currently no way to bound nesting depth under MultiAgent V2 at all.
I audited my local ~/.codex/sessions rollout logs for that day in detail and found three distinct, compounding problems:
1. Every subagent fork replays its entire ancestor lineage's bookkeeping into its own log file, verbatim
When a subagent forks, its rollout JSONL is seeded with a burst of thousands of token_count events — a byte-for-byte copy of everything every ancestor in its fork chain had already logged — all written within 1-2 seconds and re-timestamped to the fork's own creation time.
Concretely, on 2026-07-26 I had 129 sessions (root thread originally created 2026-07-17, recursively forking subagents up to 4 levels deep). Representative examples from a single fork chain (root → A → B, C, D...):
- Parent session: 23,743 total logged
token_countevents, of which 22,919 (96.5%) were themselves an inherited burst from its parent, leaving only 824 genuinely new events across its real ~4.7 hour working span. - Two children forked from that same parent: 23,135 total events (23,108 replayed / 27 real) and 23,468 total events (23,388 replayed / 80 real) respectively.
I compared the replayed burst in a child session against the parent's own historical records index-by-index: 100% of the (input_tokens, cached_input_tokens, output_tokens) triplets matched exactly, in the same order — the only difference was the timestamp. This is unambiguously a local copy/replay of already-recorded bookkeeping, not new model calls (23,000+ in ~2 seconds is not a plausible real request rate).
This inflates every downstream reporting tool that sums token_count events (e.g. ccusage — see ccusage/ccusage#950) by up to ~90x. My own reconstruction of "real" (non-replayed) usage for the day came to roughly 1.5% of what naive summation reports.
I want to be explicit that I cannot confirm from local logs alone whether this replayed data is inert with respect to my actual billed/metered usage, or whether it's also the direct cause of the real quota drain described below. If whatever system computes my account's real usage quota draws on this same per-session token telemetry — rather than exclusively on independently-verified real API request records — then this bug would not just be inflating third-party tools like ccusage, it would be inflating my actual billed usage too, once per fork in the tree. I'd like this checked directly rather than assumed away.
2. The real backend usage quota was actually drained
Regardless of how problem N1 is ultimately explained, my account's real weekly usage quota was fully consumed overnight. This is a genuine backend-side accounting problem tied to subagent spawning, not something I can attribute to a local display/logging bug alone.
From reading codex-rs/core/src/client.rs: a fresh subagent fork has no established websocket connection and no previous_response_id to chain from (prepare_websocket_request returns (None, false) when get_last_response() is empty), so every fork must open a brand-new connection and send a non-incremental first request. With a deep/wide fork tree (dozens of forks within a single hour, several clustered within the same minute), this produces a burst of new-connection establishment that looks structurally identical to the pattern that originally tripped the anti-abuse/rate-limiter bug in openai/codex#9748 (which was reported fixed server-side in February 2026, but scoped to MultiAgent V1's concurrent-spawn trigger — I could not find confirmation that the fix covers V2's tree-forking spawn pattern).
This may also be connected to the broader, still-open quota-accounting regression described in openai/codex#31668, where OpenAI's own team acknowledged anti-abuse/fraud-prevention systems overflagging ordinary usage — though that issue includes reports with no subagents involved at all, so it may be a distinct or overlapping cause rather than the same one.
3. MultiAgent V2 has no enforced nesting-depth limit at all
There is an existing config field intended to cap subagent nesting depth, agents.max_depth (agent_max_depth internally, in codex-rs/core/src/config/mod.rs). Its own doc comment says exactly why it didn't help me:
/// Maximum nesting depth for V1 agent threads. Ignored by V2.
pub agent_max_depth: i32,
I confirmed this is accurate — not just a stale comment — by reading both spawn handlers directly:
codex-rs/core/src/tools/handlers/multi_agents/spawn.rs(V1): computeschild_depthvianext_thread_spawn_depth(&session_source), readsturn.config.agent_max_depth, and callsexceeds_thread_spawn_depth_limit(child_depth, max_depth)— rejecting the spawn with"Agent depth limit reached. Solve the task yourself."if it's exceeded.codex-rs/core/src/tools/handlers/multi_agents_v2/spawn.rs(V2 — not something I opted into; see note below): computeschild_depthvia the exact samenext_thread_spawn_depthcall (line 60), but never callsexceeds_thread_spawn_depth_limit, or any other depth check, anywhere in the file.
Worth noting this isn't a niche configuration choice on my part: openai/codex#31097 documents that GPT-5.5 forces MultiAgentV2 even when it's explicitly disabled in config.toml and via command-line overrides. I was on gpt-5.6-sol, the same model line, and never selected V2 myself — so whatever is broken here isn't limited to users who deliberately opted into V2, it applies to anyone using this model family with subagents at all.
So there is currently no configuration that bounds fork-tree depth under MultiAgent V2 — not "the default is too permissive," but the enforcement path that exists for V1 has no V2 equivalent. Combined with problems N1 and N2 above, this is close to a worst case: unlimited recursion depth, where every level replays its full ancestor history into its own log (N1) and every level opens a new, non-incremental, potentially quota-draining connection (N2).
What steps can reproduce the bug?
- Start a long-lived Codex session on
gpt-5.6-solwithreasoning_effort: xhigh(MultiAgent V2 does not need to be deliberately selected — per openai/codex#31097, this model line forces V2 regardless of config). Note thatagents.max_depthhas no effect under V2 regardless of whether it's set (see problem N3), andfork_turnsdefaults to"all"/ full-history fork if left unset (per openai/codex#34061). - Instruct the orchestrator agent to delegate pieces of work to subagents (a single level of delegation, as normally expected). Do not request or configure recursive/nested spawning.
- Observe that subagents themselves spawn further subagents unprompted, several levels deep, with no depth limit enforced and full ancestor history replayed at every level.
- Let this run for several hours, accumulating dozens of forks across the resulting tree, several minutes apart or clustered within the same minute.
- Observe: (a) local rollout logs balloon with replayed bookkeeping (confirmable by diffing a child's early
token_countsequence against its parent's), and (b) the account's real weekly usage quota drains to 100% far faster than the actual new work performed would justify.
What is the expected behavior?
- Forking a subagent should not replay/duplicate the parent's entire historical
token_count/bookkeeping records into the child's own log — at minimum this should be a reference to the parent session rather than a duplicated copy, so downstream tools don't double/N-count it. - Real backend usage quota consumption should scale with actual new inference performed, not with the number and depth of forks in a session tree. Establishing a new fork's connection is a one-time, filtered-history event (per the #34061 investigation) and shouldn't be able to single-handedly exhaust a subscription's full usage window.
- Subagents should not spawn further nested subagents unless that is explicitly configured/requested. A single requested level of delegation should not silently become a multi-level recursive fork tree. Concretely:
agents.max_depthshould be enforced by MultiAgent V2's spawn handler the same way it already is for V1, not silently ignored. - If a rate-limiter/anti-abuse system is what's actually draining the quota (as in #9748 and #31668), it should not be triggered by ordinary, sequential subagent spawning under MultiAgent V2, and should never zero out the visible/billed usage quota as a side effect of being tripped.
Additional information
- Related issues that this report ties together: #9748, #22779, #31097, #34061, #33447, #31668, ccusage/ccusage#950.
- I have local rollout JSONL evidence (fork chains, byte-for-byte replay comparison, timing analysis) available if useful for debugging — happy to share sanitized excerpts privately rather than post full logs here.
14 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
It burnt my quota within 4 hours (SOL ULTRA Standard 100% to 0%). I don't know why and how.
There are some issues with the limits and usage probably!
Thanks & Regards
complete scam
Independent reproduction on 2026-08-06 (America/Montevideo).
Environment:
xhighI observed the same inherited
token_countreplay described in this issue.For the local day of 2026-08-06:
A representative child recorded 3,721
token_countevents during the same second immediately after creation. Its cumulative total jumped to 450,646,393 tokens in that burst, then reached only 453,340,154 after its actual work. The rate-limit snapshots also moved backwards during the replay burst, from a copied 64% snapshot to a copied 41% snapshot with a different reset state. This supports the conclusion that stale historical telemetry is being replayed, rather than those events representing real requests.The account’s real weekly Codex meter nevertheless reached 80%, so this was not only a third-party reporting concern. Fresh work was genuinely expensive too: about 68% of the estimated post-replay traffic came from subagents, and approximately 97% of input tokens were cached.
The exposed
spawn_agentcontract defaultsfork_turnsto"all"when omitted. For a large parent, this creates a dangerous combination:Suggested fixes:
fork_turns: "none"the default and require explicit opt-in for full-history inheritance.token_countinto child rollouts.I have retained the local session evidence and can provide redacted metadata or aggregation output if maintainers need it. I will not publish complete rollout files because they contain private conversation and tool data.
Independent reproduction on Windows Codex App.
Environment:
max_concurrent_threads_per_session = 100Observed:
task_completeturn_abortedopen.pending_init.This independently reproduces the recursive quota-consumption issue at a much greater nesting depth. The stale Desktop lifecycle/UI state also matches #37426.
Independent macOS Desktop reproduction with a single causal chain connecting unbounded MultiAgent V2 recursion, system-wide OOM, and real backend weekly-usage depletion.
Environment
26.803.415150.147.0-alpha.6.526.5.2 (25F84), Apple SiliconTrigger and agent fan-out
The root task was ordinary local work and did not request recursive delegation. It directly spawned a bounded audit worker, but descendants recursively delegated again.
Read-only reconstruction from session metadata and app-server logs found:
spawn_agenttool callsThe Desktop log also recorded 397 unique browser-use sessions and 406 unique local socket listeners during the burst. Only 181 of those sessions had a disposal record before the app was forcibly terminated.
Direct macOS memory evidence
The 17:38 Jetsam report recorded the Codex resource coalition with:
physicalPages.internalvm-compressor-space-shortageThe entire Mac became unresponsive and required forcing applications to quit. After relaunch, the old coalition was gone, which argues against unrelated persistent orphan daemons as the primary cause.
Backend usage evidence
Service-side
limit_id=codexweekly telemetry moved fromused_percent=2at 17:12:11 toused_percent=25at 17:35:09. Every integer step was present in the accident window, so at least 23 percentage points of real weekly usage were consumed during the fan-out.After globally deduplicating inherited/replayed telemetry by turn and token-usage payload, the accident window still contained:
The raw processed-token total is not presented as a billing equivalent; the direct quota evidence is the backend
used_percentsequence.Recovery/rehydration risk
After the force-quit, the affected root task was automatically restored and resumed writing. A later snapshot again showed about 22 repeated MCP/plugin helper generations and approximately 19.4 GiB RSS across the ChatGPT/Codex tree. Relaunch alone therefore does not reliably contain the abnormal agent tree.
Why this appears to be the same core defect
The current V2 spawn path computes
child_depthbut still does not perform the depth-limit rejection present in the V1 handler:Current documentation exposes
agents.max_concurrent_threads_per_session, but a hard tree-wide recursion-depth circuit breaker is not documented. A per-parent or per-session limit is insufficient if every descendant can open another independent allocation.This reproduction connects the quota failure here with the macOS OOM and task-restoration families reported in #23749, #35994, #32942, and #33700.
Requested safeguards
I retained the affected session ID, redacted Jetsam aggregates, process-tree snapshots, and sanitized log findings. I am not posting raw rollouts or full logs publicly because they contain local paths and conversation content, but they can be provided privately to OpenAI engineers.
Additional context from the postmortem: this installation had
features.multi_agent_v2.max_concurrent_threads_per_session = 1000in the user config. That value was introduced by a third-party LazyCodex/OMO migration guard, so it substantially amplified the incident compared with Codex's current source defaults. The same third-party guard preserves an existing explicit V2 cap, so I have now reduced it locally to 6 and installed a higher-precedence managed config withagents.max_concurrent_threads_per_session = 6,agents.max_depth = 1, and a no-recursive-delegation instruction for V2 subagents.This local factor is important for reproduction and attribution, but the upstream safety gap remains: one root task was still able to recursively fan out to depth 14, hundreds of concurrent descendants, and large quota/resource consumption without a built-in hard depth/cost/circuit-breaker stop. A defensive product cap should prevent a plugin or user config from turning one mistaken delegation loop into a system-wide failure.
Independent reproduction on macOS with GPT-5.6 Sol xhigh and real weekly quota exhaustion.
Environment / observed account impact:
Local rollout evidence from
~/.codex/sessions:codex doctorreported 63subagent:thread_spawnrowsThe Aug 15 large rollouts are strikingly similar in size and historical structure, consistent with children/forks inheriting a very large ancestor history. Examples:
| Size | token_count occurrences |
"type":"compacted"occurrences | inline image occurrences | task_complete occurrences | turn_aborted occurrences ||---:|---:|---:|---:|---:|---:|
| 6.17 GiB | 18,684 | 238 | 5,801 | 439 | 48 |
| 6.14 GiB | 18,799 | 237 | 5,776 | 438 | 48 |
| 6.08 GiB | 18,639 | 235 | 5,726 | 438 | 48 |
| 6.08 GiB | 18,584 | 235 | 5,726 | 438 | 48 |
| 6.02 GiB | 18,433 | 233 | 5,676 | 438 | 48 |
| 6.02 GiB | 18,432 | 233 | 5,676 | 438 | 48 |
| 5.99 GiB | 18,423 | 232 | 5,651 | 438 | 48 |
| 5.99 GiB | 18,399 | 232 | 5,651 | 438 | 48 |
| 5.99 GiB | 18,308 | 232 | 5,651 | 437 | 48 |
One additional clue: in those files there are ~232-238 textual occurrences of
"type":"compacted", but only 10 top-levelcompactedJSONL records in each when parsing records directly. That suggests most of those historical compaction markers live inside inherited/replayed payloads rather than representing fresh compactions in each child. The same files also contain ~5.6k-5.8k inline image occurrences and ~438 historicaltask_completeoccurrences each.I attempted a privacy-safe token-history prefix comparison, but my first parser did not match the current token_count payload shape and returned zero parsed records, so I am not claiming a measured common-prefix percentage yet.
Evidence boundary:
Representative affected session IDs:
01a007b0-47a9-7113-852c-178f5de17d4a01a007c6-d93a-7f51-94de-a5f59e72b4aa01a007aa-ae99-7e23-8be1-6d53f1accb1c01a007b4-6738-7480-801a-d23b1f90490f01a0077e-8f65-7cb3-9c6c-70d5efdf7dc201a0078b-c0e5-72b3-baf7-9762855fc186This looks closely related to the inherited-history / recursive subagent behavior described in this issue. I can provide additional privacy-safe structural statistics from the local rollouts if maintainers want a specific comparison.
The recursively forked subagent/session tree in this report is useful for a different part of
codex-rescuefield validation: session discovery and conservative diagnosis across large inherited histories. It won’t fix quota accounting or MultiAgent V2 recursion; I’m specifically testing persisted-state handling without mutating the original rollouts.If you still have the affected session tree locally, could you run:
I’d like to know whether discovery remains sane with the deep fork tree and whether
doctoravoids treating copied bookkeeping as evidence that an action executed. If diagnosis finds a genuinely unfinished action and recovery is needed, the optional path issalvage --latest --forkfollowed byverify <rescue-id>.Please share only sanitized output, versions and exit codes; no raw rollout/SQLite files, prompts, credentials, or private paths. Repo: https://github.com/shleder/codex-rescue
I can provide an additional privacy-safe data point for this issue.
Environment:
Observed account impact:
Privacy-safe local structural findings for that same window:
This is not the same deep recursive tree reported in the issue: I did not observe depth 4+ or evidence sufficient to claim unbounded fan-out. It is nevertheless a similarly severe weekly-quota depletion with shallow observed topology.
The local metadata included Sol/high, Sol/max, Sol/xhigh, and a small number of Sol/ultra-labelled child records. I did not intentionally select Ultra for those child runs; two had a Sol/max parent. I am not claiming that the local
tokens_usedfield equals billed usage, because both parent and child counters can be nonzero and naïve summation is unsafe.I can provide further sanitized structural statistics if OpenAI engineers specify the exact fields needed. I will not post raw rollout files, prompts, local paths, session IDs, or credentials publicly.
Additional workload context: on Friday, August 14, I was away for most of the day and did very little direct Codex work until the evening. The severe weekly-quota depletion therefore occurred during materially lower hands-on activity than my normal work pattern. This does not by itself prove background usage; it is a workload-control observation.
Independent reproduction on macOS Desktop with the stale-active-state symptom and possible quota concern.
Environment:
0.148.0-alpha.9Observed:
not_foundfor each child ID.``
toml
``[agents]
enabled = false
max_concurrent_threads_per_session = 1
This appears to overlap the recursive quota problem reported here and the stale lifecycle/UI state in #37426. The especially concerning part is that a full restart with agents disabled did not clear the active tree.
Expected:
I have screenshots showing 415 before restart and 525 after restart, plus sanitized app logs, if maintainers want them through a private diagnostic channel.
Additional Linux reproduction with a flat, depth-1 V1 agent topology.
Environment:
I found seven direct children created with
fork_context: true. Each child rollout received a dense copied prefix immediately after creation:For every affected child, I compared the copied prefix with the parent history index by index. The
(input_tokens, cached_input_tokens, output_tokens)tuples matched the parent sequence exactly: 100% match, in the same order.The copied cumulative baselines were between 850,391,164 and 1,260,303,143 tokens. As a result, the child rows in the state database reported about 861M-1.273B
tokens_usedeach. After removing the copied prefix and unchanged cumulative snapshots, the actual new work was only 20-86 token events and approximately 1.38M-12.68M tokens per child.Across the seven children, naive local accounting therefore included 8.334B tokens of inherited cumulative baselines. The cloned rollouts also copied historical task, compaction, and session metadata, so counts of those records are inflated for the same reason.
One metadata caveat may help diagnosis: the live executable reported 0.149.0, while affected child metadata reported 0.148.0. The child metadata is part of the cloned state and is not a reliable runtime-version source in this case.
The visible server-side weekly meter also increased by 39 percentage points in the same 24-hour observation window. However, local evidence cannot prove that the copied 8.334B records were billed. After replay correction, genuine processing across the five observed task families was approximately 1.915B tokens. I am reporting the copied history as a confirmed persistence/telemetry defect, while keeping its billing effect explicitly unproven.
Expected behavior:
tokens_usedas child usage.No prompts, task identifiers, repository names, local paths, account data, or credentials are included.
Before deleting anything to reclaim disk/quota:
vetto rescue --json scaninventories actual per-session rollout bytes read-only, so you can see which sessions genuinely ballooned versus what the UI misreports. It never writes inside ~/.codex, so the inventory step cannot make things worse.