pingdotgg / pingdotgg/t3code

delegate_task mode:"wait" dies at the client ~5 min ceiling and returns no taskId, causing duplicate child dispatch

Open
#11,168 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug via-triage
Dominant language
TypeScript
Stars
23k
Forks
5.9k
Avg merge
11h 14m
Merged PRs (30d)
357

Description

Summary

delegate_task with mode: "wait" is unsurvivable for any child that runs longer than ~5 minutes when the MCP client is Node-based (Pi provider here, but this is true of any fetch/undici client). The wait is a single synchronous MCP request held open for the child's entire run; undici's default headersTimeout is 300 s, so the HTTP request dies first and the caller gets a bare fetch failedwith no taskId and no childThreadId.

The consequence is worse than a lost result: the parent agent has no handle to reconcile with, cannot call task_status, and cannot tell "the child never launched" from "the child is running fine". The only apparent recourse is re-dispatch — which spawns a second child writing into the same cwd.

Evidence (two independent occurrences, same day, same fingerprint)
parent A parent B
child dispatched 21:20:12Z 01:10:01Z
child actually ran to completed 21:55:50Z 01:15:03Z
parent's wait call failed → duplicate re-dispatch 21:25:52Z (+5m40s) 01:15:52Z (+5m51s)
duplicate child interrupted 21:27:02Z interrupted 01:18:17Z

Both retries landed at ≈5m45s after dispatch — the undici 300 s ceiling plus one agent turn. Control: a trivial mode:"wait" child (seconds long) returns a clean, complete envelope (taskId, childThreadId, status: completed, summary), so wait-mode itself is fine; only its duration tolerance is broken.

In the second case the duplicate child was smart enough to notice the first child's commit and refuse to redo the work. That was luck, not a guarantee — two concurrent writers in one working tree is the real hazard.

Where the mismatch lives

apps/server/src/mcp/OrchestratorMcpService.ts clamps the wait budget to a default of 10 minutes and a maximum of 60 minutes. Both sit well above the ~5-minute ceiling a Node fetch client can hold a response open, and nothing is emitted on the wire during the wait to keep the connection alive. The tool description also advertises waitTimedOut + "keep that taskId and read status on later task_status" — but that graceful path is only reachable when the server's own timer fires first, which by construction it cannot for the default budget.

Related: when the parent's turn was handed off/interrupted while a wait was in flight, the delegated child was interrupted too, so a child's lifetime appears coupled to the parent's in-flight tool call.

Suggested fixes (any one helps; 1+3 would close it)
  1. Emit MCP progress notifications (or SSE keep-alives) during mode: "wait". Traffic on the stream resets the client's header/body timers, which makes long waits viable and is the behavior most MCP clients expect for long-running tools.
  2. Or clamp the default/max wait below the client ceiling (e.g. default 120 s, max 240 s) and return the documented waitTimedOut: true envelope, which at least preserves the taskId.
  3. Make the taskId observable before the wait resolves — e.g. send it in an early progress notification, so a parent whose wait died can reconcile via task_status instead of re-dispatching. Documenting "on any delegate_task transport error, call t3_thread_list before retrying" would also help agents, since a server-generated clientRequestId cannot dedupe a retry (the caller never saw it).
Environment

macOS 15, Pi provider (anthropic/claude-opus-5), full-access runtime. Build is a local fork tracking the Orchestrator V2 branch (PR #2829) at 8f44bec / 0.0.40, with two unrelated local patches (Pi in the Usage dashboard; delegated-wake cap removal) — neither touches the MCP layer. The code paths cited are upstream-unmodified.

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.

Research direction

Start with apps/server/src/mcp/OrchestratorMcpService.ts, focusing on delegate_task wait budgets, timeout handling, and response delivery. Reproduce a wait longer than five minutes with a Node fetch/undici client, then trace whether progress or timeout responses preserve taskId and childThreadId. Done means long waits do not fail without a reconciliation handle or cause duplicate child dispatch.

Written by the indexing model from the issue text.

Assessment

Tech stack
node.js, typescript
Domain
api, backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.