[Codex Desktop] Nested functions.exec wait_agent polling causes repeated parent turns and token usage

Open
#35,108 5 comments 4 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
5/5
Estimated time
Over a week
Newbie friendliness
35/100
Issue type
Bug
Clarity
Mostly clear
Activity status
Active
Tech stack
javascript, rust

Research direction

Start at the multi_agent_v1__wait_agent entry point and trace how it is routed through functions.exec and functions.wait. Reproduce the nested wait with timeout_ms: 60000 and inspect the parent/model resumptions and wrapper metadata. Done means the runtime keeps the parent suspended until completion, failure, or timeout without repeated model inference for internal polling.

Written by the indexing model from the issue text.

Description

app bug performance subagent tool-calls windows-os

Summary

When multi_agent_v1__wait_agent is invoked inside Codex Desktop's generic functions.exec code-mode wrapper, one logical wait can be split into multiple parent-visible tool boundaries:

  1. the outer functions.exec yields with Script running with cell ID ...;
  2. the parent then invokes functions.wait;
  3. the wait returns timed_out: true;
  4. the parent model is sampled again after each boundary.

This appears to consume additional model turns and append low-value wrapper metadata while the child agent is still simply running. The issue is not that the model continuously generates tokens during the blocked interval; the problem is that each yield/timeout returns control to the parent model and may trigger another full-context inference.

This is related to, but distinct from, the 30-second default timeout reported in #18394: the reproduction below explicitly passes timeout_ms: 60000.

Environment

  • Surface: Codex Desktop
  • Platform: Windows 11 x64
  • Observed package version: 26.721.3404.0
  • Tool path: nested multi_agent_v1__wait_agent through generic functions.exec
  • Multi-agent version: v1

Reproduction

  1. Spawn a subagent and keep its work running longer than the outer code-mode yield interval.
  2. In the parent, invoke the wait through a generic functions.exec wrapper:
const result = await tools.multi_agent_v1__wait_agent({
  targets: [agentId],
  timeout_ms: 60000,
});
text(result);
  1. In the observed run:
    • the nested wait_agent started with timeout_ms: 60000;
    • after approximately 11 seconds, the outer call returned Script running with cell ID 29;
    • the parent then called functions.wait for the cell;
    • approximately 50 seconds later, functions.wait returned {"timed_out":true}.

Observed usage evidence

The local rollout recorded two consecutive usage events around this single logical wait:

  • first event: input 163,952; cached input 162,560; output 126; total 164,078;
  • second event: input 164,106; cached input 163,584; output 31; total 164,137.

These raw totals are mostly cached input and should not automatically be interpreted as billable tokens. They do show that the parent turn resumed twice and that the second resume reprocessed an almost equally large context.

The important symptom is the repeated parent/model boundary:

multi_agent_v1__wait_agent(timeout_ms=60000)
  -> functions.exec: Script running with cell ID ...
  -> functions.wait(...)
  -> timed_out: true
  -> another parent/model continuation

Expected behavior

  • A parent waiting for a subagent should remain in a runtime-managed wait until the child completes, fails, or the requested timeout expires.
  • An intermediate wait yield should not require a new full parent-model inference.
  • If an internal cap exists, the runtime should re-arm the wait internally rather than return to the model for every interval.
  • Status/heartbeat metadata should not be repeatedly appended to the model-visible conversation history.
  • timeout_ms should bound the complete multi-agent wait operation, including the outer tool wrapper.

Actual behavior

  • One logical wait_agent operation is exposed as multiple code-mode/tool polling steps.
  • Each step returns control to the parent model.
  • The parent receives verbose wrapper output such as Script running with cell ID ... and then a separate timeout result.
  • With a large parent context, this can create substantial repeated input processing and token/quota consumption.
  • The behavior remains present even when wait_agent receives an explicit 60-second timeout.

Related issues

  • #18394 — default wait_agent timeout is hardcoded to approximately 30 seconds and causes polling loops.
  • #32640 — the built-in wait tool is capped at approximately 50 seconds and re-samples the full context on each expiry.
  • #29122 — long-running nested tools routed through functions.exec consume tokens while the model repeatedly waits.
  • #13733 — background polling sends a full API turn with the complete history for each poll.
  • #24951 — wait_agent.timeout_ms may not bound the complete tool call.

Those issues cover adjacent generic wait, code-mode, or multi-agent behavior. I did not find a report specifically covering multi_agent_v1__wait_agent nested inside Codex Desktop's functions.exec wrapper and producing multiple parent usage events for one logical wait.

Suggested fix direction

  • Keep multi-agent waits as direct runtime-managed operations instead of wrapping them in a model-polled code-mode cell.
  • If a wrapper is unavoidable, keep the parent turn suspended until the child reaches a terminal state or the full timeout expires.
  • Re-arm internal timers without a new model request.
  • Avoid adding repeated cell IDs, wall times, and unchanged wait metadata to the model-visible history.
  • Add telemetry that distinguishes one logical wait from internal runtime polling and reports the number of parent model resumptions.
Dominant language
Rust
Stars
125k
Forks
19.5k
Avg merge
1m
Merged PRs (30d)
1k

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from openai/codex

All issues in openai/codex

Similar issues

More Rust issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.