Skip to content

[Bug]: Batched delegate_task wait calls start children one at a time, but the parent reports both running #23175

Open

Description

@Artem-B

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Area

apps/server

Steps to reproduce

  1. Start a thread with Codex or Claude Code as the agent and the T3 delegation tools available.
  2. Have the agent submit two delegate_task calls together. Use mode: "wait", timeoutMs: 10000, and a read-only child task that runs longer than the wait.
    • In Codex code mode, run the cell below in one functions.exec call.
    • In Claude Code, have the agent emit the same two calls as parallel tool uses in one assistant message.
  3. In the server traces, compare when T3 receives each delegate_task HTTP request with when the first call returns.
const runId = "parallel-probe-1"; // use a new value for each run
const results = await Promise.allSettled(
	["a", "b"].map((seat) =>
		tools.mcp__t3_code__delegate_task({
			clientRequestId: `${runId}-${seat}`,
			title: `Parallel delegation probe ${seat}`,
			target: {
				providerInstanceId: "codex",
				model: "gpt-6.1-sol",
				options: { reasoningEffort: "high", serviceTier: "default" },
			},
			isolation: "shared",
			mode: "wait",
			timeoutMs: 10000,
			task: "Run `sleep 15`, then reply PROBE_COMPLETE. Do not edit files or delegate.",
		}),
	),
);
results.forEach((result) => text(JSON.stringify(result)));

I did not run this exact cell. The tested runs gave the children the same work through file-backed prompts. The Claude Code run used these wait and delay values. The Codex runs used shorter and longer waits.

Expected behavior

When an agent intends to run two children in parallel, both children start. The agent reports a child as running only after T3 returns its taskId and a running status.

T3 does not promise that mode: "wait" calls dispatch in parallel. The mode description says only that a wait blocks until completion, a blocking question, a pending approval, or timeout. It does not tell the agent that a blocking call can hold back other calls submitted with it. Either the calls dispatch concurrently, or the guidance tells the agent to launch every child with mode: "async" before it waits on any of them.

Actual behavior

The second child starts only after the first wait returns. In the original incident, the parent reported both as running before the second child existed.

In the controlled Claude Code run, both complete tool uses were in one assistant message. B's arguments were ready about 8 seconds before A's response finished. Even so, T3 received B's HTTP request 18 ms after A's response finished. Codex code mode showed the same order with Promise.allSettled.

In the original Codex incident, only the first child existed after about two minutes, but the agent reported both as running. The user found the missing child. The agent then launched it with mode: "async", and T3 accepted that call while the first wait was still open. The original batched call reached T3 about three and a half minutes later, after the first wait returned. It reused the same clientRequestId, so it returned the existing child. No request was lost, no transport failed, and T3 created no duplicate child.

The delay happens before T3 receives the second request. T3 accepted another delegation during the wait, so T3 does not hold a server-wide lock. This report covers two calling paths, Codex code mode and Claude Code direct parallel tool uses, not every runtime or MCP tool. The scheduling policy inside each runtime is unknown. Whether T3 can change its runtime integration is an open question, but T3 owns the launch guidance that agents follow.

Related issues, with no confirmed duplicate:

Impact

Minor bug or occasional failure

Version or commit

0.1.2-nightly.20261021.3473

Environment

Debian 13 Linux backend; Codex MCP client 0.163.0; provider/model codex / gpt-6.1-sol;

Logs or stack traces

# All times UTC on 2026-10-21. A and B are the two delegate_task calls.

# Controlled run: Claude Code parent, 10000 ms waits, children run past the wait
23:58:54.843  A: complete tool-use arguments emitted
23:58:54.853  T3 receives A HTTP request
23:58:56.879  B: complete tool-use arguments emitted in the same assistant message
23:59:04.923  A: HTTP response finishes, waitTimedOut=true
23:59:04.941  T3 receives B HTTP request
23:59:14.993  B: HTTP response finishes, waitTimedOut=true

# Original incident: Codex code mode, Promise.allSettled, 30-minute waits
23:24:02.000  T3 receives A HTTP request
23:25:50.530  Thread list shows only child A
23:25:58.367  T3 receives a separate async request for B
23:25:58.412  T3 creates child B
23:29:22.898  A: MCP call completes
23:29:22.900  T3 receives the original batched B request

Screenshots, recordings, or supporting files

No response

Workaround

Launch every child with mode: "async" and keep each returned taskId. Report a child as running only after T3 returns its taskId and a running status. Start any blocking wait only after every launch returns.

I have a local change that adds this order to the orchestration instructions and to the delegate_task and mode descriptions. It changes only the guidance, not how calls dispatch.

Activity

  1. added
    bugSomething is broken or behaving incorrectly.
    needs-triageIssue needs maintainer review and initial categorization.
    on Oct 22, 2026
  2. juliusmarminge commented on Oct 22, 2026

    @juliusmarminge
    Member

    Triage: no matching duplicate found among 940 open issues, and no matching work found among 1,050 open PRs scanned through the 90-day cutoff. No issue comments or linked PRs were present. The existing wait-timeout issue is a different symptom.

    Read-only inspection at 07d38b8 confirms the delegate_task description only recommends async for long work; it does not describe launching all children before waiting. No runtime reproduction was performed. Routing to Julius because the proposed change touches MCP tool guidance, which is outside the automated triage fix scope. Leaving this issue open; no fix PR created.

  3. juliusmarminge commented on Oct 22, 2026

    @juliusmarminge
    Member

    Reproduced the Codex batching delay: two 10-second waits submitted through Promise.allSettled; B started 74 ms after A's wait returned. Async launches returned both running task IDs within 795 ms.

    The fix adds async-first launch guidance and requires confirmation before reporting a child as running. Verified the actual provider context and MCP tool descriptions on current-main dev head 5f90bd3add; both async probes completed. The 16 focused instruction-delivery tests pass.

    Fix: #23241. Runtime evidence. Claude direct parallel tool use was not independently rerun.

  4. Majkey25 commented on Oct 30, 2026

    @Majkey25

    Adding a concrete use case for the parallel-dispatch behavior here: I want a parent agent to launch many independent OpenCode subagents using the same provider instance and model (for example, multiple Nemotron or DeepSeek workers), without switching models or opening separate top-level threads.

    My parent agent said it could only launch one, but I don't have the underlying error/log yet, so I can't say whether this is the mode: "wait" serialization reported here or a separate constraint. #24967 already addressed the instruction-level "one child per named model" rule, but I'd also like true concurrent dispatch to be reliable.

    A useful acceptance test would launch 2, then 10+ same-model children via mode: "async" before waiting, verify distinct taskIds and actual overlapping run times, and surface a clear concurrency-limit error if the provider/hardware cannot handle the requested count. Ideally the user could choose a reasonable concurrency cap (potentially dozens, e.g. up to 50, subject to resources) instead of hitting an unexplained one-child limit.

  5. thejug923 commented on Nov 12, 2026

    @thejug923

    Another occurrence, with the Claude Code side confirmed from the shipped binary.

    Environment

    • T3 Code desktop 0.1.9, local server, Windows 11
    • Parent: Claude Code 2.1.322, claude-opus-5-5
    • Children: claudeAgent / claude-fable-5-2 and codex / gpt-6.1-sol

    What happened

    A skill told the parent to start two reviewers in one assistant message, each with delegate_task, mode: "wait" and timeoutMs: 1800000. The first child started at once. The second call never reached the server, and T3 showed it as in progress the whole time, so to the user it looked hung. After about two and a half minutes the user interrupted. Call 1 came back interrupted, and call 2 came back rejected without ever having run.

    Resent alone with mode: "async", the second call returned in about 0.1 s. Later in the same session, the parent sent two mode: "async" calls in one message. Both returned in about 0.1 s, ran side by side, and each woke the parent when it finished.

    The server trace from an earlier run on the same machine shows runs that "worked" were serial too. B's request arrived in the same second A's wait returned: A took 634 s, then B took 279 s. A second pair in that run did the same, 188 s then 342 s.

    Why, on the Claude Code side

    Claude Code decides for each MCP tool whether its calls may run alongside others. From the 2.1.322 binary:

    isConcurrencySafe(){return Ye.mayOverlap||(ne.annotations?.readOnlyHint??!1)}

    delegate_task is annotated Destructive: true and OpenWorld: true, with no read-only hint. So Claude Code always runs batched delegate_task calls one at a time. Claude Code's MCP docs say the same of readOnlyHint: "Claude Code also runs the tool's calls in parallel with other read-only calls." #34763 reproduces it with a stub server. For Claude parents this is deterministic, not a race.

    Status on main

    The mode description still reads "Default 'async'. 'wait' only if this turn needs the result." orchestrationInstructions.ts has no rule to launch every child async before waiting. The fix linked above points to #23241 on git.cafe, but GitHub's #23241 is an unrelated mobile fix, so I couldn't confirm that the guidance ever shipped. A parent running a review panel reasonably reads "this turn needs the result" as a yes, so the current wording doesn't stop the batch.

    Suggestion

    Add a line like this to the mode description and the orchestration instructions: "Several children: start each with mode: \"async\" and follow its next. Batched wait calls run one at a time." That's the same direction #34763 takes for mixed fan-out.

    Workaround in use

    Start every child with mode: "async" in one message, end the turn, and continue once every completion notice has arrived.

    Written during a T3 triage pass by Claude Code (claude-opus-5-5).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething is broken or behaving incorrectly.needs-triageIssue needs maintainer review and initial categorization.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions