multi_agent: wait_agent keeps timing out after terminal agent state is observable
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 125k
- Forks
- 19.4k
- PR merge metrics
- PR metrics pending
Description
What version of the Codex App are you using?
26.820.71523 (VS Code extension host spawning codex.exe), Windows x64
What issue are you seeing?
Summary
With [features] multi_agent = true, a spawned child turn is recorded as completed,
and no explicit error event was recorded in its rollout. A wait_agent call returned
{"timed_out": false} during the child's normal completion at 13:56:55.696 UTC.
After the first completed-state observation at 14:01:24.689 UTC, the terminal state
was exposed by interrupt_agent and list_agents, but subsequent observed
wait_agent calls associated with the agent produced {"timed_out": true} outputs.
The target attribution of send_message calls remains inferential because the
logged arguments contain no recipient ID. The trace does not yet isolate whether
the repeated calls were generated by runtime supervisor logic, model decisions, or
both; the missing safety boundary is observable regardless of that distinction.
In the directly verified 16:04:11–16:59:59 UTC window, 93 send_message calls and
120 wait_agent calls were recorded; all 120 wait_agent outputs were
{"timed_out": true}. Other tool calls were present but are not included in this
213-call send/wait count.
Using the strict post-completion boundary, 136 observed wait_agent calls produced
{"timed_out": true} outputs by 17:11:51.454 UTC. The fixed metering window from
16:02:37.741 to 17:11:51.454 UTC showed a client-reported cumulative input increase
of 44,500,664 tokens across 254 non-zero increments (mean 175,199; median 174,771;
min 157,837; max 188,247). Cached input increased by 43,780,736, approximately
98.4% of the window increase. This is consistent with frequent repeated
large-context model turns; request-level tracing is needed to confirm exact context
resubmission and cache behavior.
The trace shows repeated model/tool round-trips without an effective runtime safety
bound. During collection, the session continued beyond
the fixed measurement cutoff. The affected rollout file was later observed to stop
receiving new data; the cause and exact stop time were not determined by this
read-only check. A related later trace (see Related reproduction below) exhibited
what appears to be the same missing terminal-target / no-progress safeguards in a
different form; this is an observational comparison, not a confirmed shared root
cause.
Timeline (UTC)
- Aug 31 13:55 — main agent spawns child agent
/root/sam3_examples_inventory
(spawn_agent,fork_turns="all"). - Aug 31 13:56–14:01 — the child turn is recorded as completed, and no explicit
error event was recorded in the child rollout (82 lines, ending 13:56:55.043).
Await_agentcall returned{"timed_out": false}at 13:56:55.696 as the child
finished. The first observed completed state for the child agent in the main
session is at 14:01:24.689 UTC, whereinterrupt_agentexposed the
already-completed state in itsprevious_status.completedfield (six times by
17:11:51);list_agentsalso reports the agent as{"completed": …}(16:11,
16:52, 17:10). - Aug 31 14:03 → 17:11+ — after the first completed-state observation,
message/wait activity associated with the agent continued. After 14:01:24.689
UTC, 136 observedwait_agentcalls produced"timed_out": trueoutputs by
the fixed 17:11:51.454 UTC cutoff. 116send_messagecalls were recorded from
the 14:01 UTC minute onward; recipient attribution remains inferential. The densest
verified continuous window was 16:04–16:59 (56 min).
The per-turn pattern (excerpt, UTC)
16:04:11 CALL send_message
16:04:18 CALL wait_agent
16:04:28 OUT wait_agent {"timed_out": true} (timeout_ms=10000)
16:04:34 CALL wait_agent
16:04:44 OUT wait_agent {"timed_out": true}
… (repeats; timeout/tool round-trips were followed by metering
increments of approximately 175K input tokens each)
16:11:13 OUT list_agents → agent status is completed
… subsequent wait_agent calls continue to time out …
16:50:30 OUT interrupt_agent → previous_status.completed
… subsequent wait_agent calls continue to time out …
During the same period, the UI repeatedly displayed "agent updated" events, making
the stalled session appear active. I have not correlated each UI event to an
individual tool call.
Token accounting (client-reported token_count events, fixed cutoff)
- Window: 16:02:37.741 → 17:11:51.454 UTC (single fixed cutoff, not live-updated)
- Cumulative input counter: 554,135,398 → 598,636,062 = +44,500,664 tokens
- 254 non-zero increments: mean 175,199; median 174,771; min 157,837; max 188,247
- Cached input rose by 43,780,736 tokens ≈ 98.4% of the window increase
- These are consistent with repeated large-context model turns, but request-level
tracing would be needed to confirm exact context resubmission and cache behavior - The session-level cumulative counter also contains earlier (pre-incident) usage;
only the window increments above are attributed to this behavior
Diagnostics that may help locate the state-propagation issue
- In the local state databases, the child thread has a completed turn while the
correspondingthread_spawn_edgesrow still has statusopen. I have not
confirmed whetheropenmeans an active child or merely an unclosed parent-child
relationship. The discrepancy between the turn state and the edge state is a
candidate area for investigating whylist_agents/interrupt_agentexpose the
terminal state whilewait_agentcontinues to time out; its causal role is
unconfirmed. logs_2.sqliterecorded fourERROR-level entries from
codex_core::tools::router(core/src/tools/router.rs:323) around
13:55–14:02 UTC, at least one involvingsend_message. Not established as causal;
sanitized excerpts available on request (encrypted payloads and user file contents
removed).
Recurrence
A related long-running polling pattern appeared earlier: on Aug 29, 17:46–21:05
UTC (3h19m), the main session polled list_agents roughly every 20 seconds
(618 responses; median interval 19.0s) with no observed deadline or budget stopping
it. I have not established that the earlier agent was already completed, or that the
earlier sequence shares the same lifecycle root cause.
Related reproduction (later trace)
In a separate session on Sep 1, a child was interrupted approximately three seconds
after spawn_agent (turn_aborted was recorded and the child produced no normal
function-call activity). The parent then continued issuing send_message calls.
Unlike the main trace, the tool arguments explicitly contained target: "/root";
whether /root was the source agent itself cannot be established from the persisted
source identity alone. This is therefore evidence of a permitted root-target message
and a strong lead for missing self-target validation, but not yet proof of an A→A
delivery loop.
The session issued 1,615 send_message calls in total; one occurred 28 ms before
the first token_count event used below, so 1,614 fell within the metering window.
The send intervals had a median of approximately 6.7 seconds and a minimum of 2.2
seconds (the maximum gap was ~16 minutes, so the sending was not uniformly
high-frequency). From the first token_count event at 10:13:26.108 UTC (24,605)
to the last at 14:38:42.185 UTC (103,229,651), the client-reported cumulative input
counter increased by 103,205,046 tokens. Note that the send-call count
(function-call timestamps) and the token-counter delta (token_count event
endpoints) are different event types and are not expected to match one-to-one.
These observations do not by themselves
establish whether the repetition was generated by runtime orchestration, model
decisions, message replay, or some combination; they demonstrate that no effective
no-progress safety bound stopped the activity during the observed period.
Expected behavior
- Sticky, authoritative terminal state: once an agent reaches a terminal state,
wait_agentshould consult the authoritative lifecycle state and return that
terminal state immediately and idempotently. Await_agentcall for an
already-completed generation must not wait for a new event; it should return the
persisted terminal result. If a new turn is intended, it must use an explicit new
generation/operation ID — a completed generation must not be reopened merely
becausesend_messageis issued against it.wait_agent,list_agents, and
interrupt_agentshould use one authoritative state model or provide equivalent
versioned/linearizable lifecycle semantics. In this trace,list_agentsreported
the agent as completed andinterrupt_agentexposed
previous_status.completed, while the observedwait_agentcalls after
14:01:24.689 UTC continued to time out. - Explicit semantics for messaging terminal agents: if the orchestration is
messaging an already-terminal agent, the runtime should either reject the
operation with an explicit terminal-state error or create an explicit new
lifecycle/generation. Because the loggedsend_messagearguments do not include
a recipient identifier in the main trace, target attribution remains inferential.
The related trace below additionally suggests the runtime should reject
self-targeted messages — compared using internal agent/generation identifiers,
not display names such as/root— with an explicit error, and keep the
user-visible commentary channel separate from internalsend_message. - Runtime-level waiting/backoff: in this trace, repeated
wait_agent(timeout_ms=10000)calls whose outputs were"timed_out": truewere
followed by new model turns reporting approximately 175K input tokens each,
predominantly as cached input. Keeping the polling/backoff loop below the model
layer would avoid repeatedly invoking the model solely to continue waiting. - No-progress circuit breaker / budget: stop the loop based on multiple signals
(consecutive timeouts, total wall-clock deadline, duration without any agent-state
change, cumulative input-token growth, repeated identical tool sequences), then
surface options to the user such as: use completed result (when the final message
is retrievable) / restart agent / cancel / export diagnostics. - Honest UI state: distinguish "actively working" from "waiting on a completed
agent" — repeated "agent updated" events made the stalled loop look like progress.
Notes / what I have not verified
- The
send_messagemessage bodies are encrypted in the rollout logs, and the
recipient agent identifier is not present in the logged arguments; target
attribution to the completed agent is inferred from the surrounding orchestration
context (interleavedwait_agent/list_agents/interrupt_agentcalls referencing
it). - I have not confirmed at source level whether
send_messageto a completed agent
errors internally; the observable behavior is that subsequent waits timed out even
after the completed state had become visible, while the terminal state was
exposed viainterrupt_agentandlist_agents. - The trace contains one normal-completion
wait_agentresponse with
"timed_out": falsebefore the later completed-state observation. This shows the
behavior is not an unconditional timeout on every path, but it does not establish
the intended semantics of waiting on an already-terminal generation. - The meaning of the
openspawn-edge status (active child vs. unclosed
relationship) is not confirmed. - Token counters are client-reported metering values; billing impact of cached vs
uncached input is not addressed here. - All counts use the fixed cutoff 17:11:51 UTC; the strict post-completion boundary
is 14:01:24.689 UTC (first completed-state observation). For reproducibility: a
broader count starting at the 14:01:00 UTC minute and using output timestamps
yields 138 timeouts; that broader count is not the strict post-completion count.
Related issues
- #39854 (abnormal ~678M token consumption via repeated multi-agent polling)
- #24951 (
wait_agent/spawn_agentcan block for hours; timeout not enforced) - #41875 (align
wait_agentdefault timeout with prompt-cache TTL) - #23700 (stale Codex subagents)
- #37299 (wait/status orchestration re-meters full cached context every 10–30s)
Happy to provide additional sanitized excerpts (encrypted payloads and user file
contents removed) if useful.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by tracing the wait_agent, list_agents, and interrupt_agent lifecycle paths and compare them with the completed turn and open thread_spawn_edges state. Inspect the router error location at core/src/tools/router.rs:323 and reproduce the terminal-agent sequence if possible. Done should include an agreed, tested boundary for terminal agents and bounded waiting, but the issue does not identify specific tests or files beyond the router location.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- backend-api-design, distributed-systems
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Needs clarification
- Newbie friendliness
- 30/100