Repository navigation
fix(server): a follow-up to a finished remote task is a new task that wakes the parent - #26776
juliusmarminge wants to merge 5 commits into
Conversation
…tarts no task A t3_thread_send to a finished delegated task's thread in a linked environment returns no taskId, and nothing here follows the run it starts there, so the parent is never woken with its reply. These tests reproduce #26740. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A parent's t3_thread_send to the thread of one of its remote delegated tasks that already had its result went to the linked environment and came back unchanged: there the thread's parent is elsewhere, and here nothing recorded or followed the run it started, so the parent was never woken with its reply. The forwarded send now goes through RemoteDelegation.followUp, which records the started or queued run as a new remote task with the existing delegated_task.remote.request command (keyed by the send's message id, so a retry records once), follows it, and returns its taskId, as a follow-up to a finished local child does. McpToolAccess.afterForwarding lets a declaration act on a result its call brought back from a linked environment. A follower's stop key now names the task's run, because several tasks can share a thread there and each counts its own stops. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ke local ones A follow-up task's follower waited for the whole remote thread to settle and took its answer, so a run queued behind it as another task could complete it with that task's reply. A settled answer that another task here owns now leaves this task with its own run's reply. A follow-up sent while only a background agent kept the caller live started work there but recorded no task. Follow-ups now bind to the latest active run, else the last that ran, as local follow-ups do, and the remote request decider accepts that run for a follow-up to a finished task on the same thread. The record-failed error advises a resend only when a clientRequestId was given. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A follow-up task there whose thread settled on a later run took the wrong reply when either run was restarted: ownership matched original run ids, so a sibling's restart continuation looked like this task's own answer, and the fallback read this task's interrupted run instead of its continuation. Now a task takes the thread's answer only when no task here on the same thread was recorded at or after it; otherwise it takes its own run's latest restart continuation, the run its wait resolved to. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| other.remoteChild?.environmentId === remote.environmentId && | ||
| other.remoteChild.threadId === remote.threadId && | ||
| // One recorded in the same instant may be later too. | ||
| (recordedAt === null || |
There was a problem hiding this comment.
🟠 High peer/RemoteDelegation.ts:743
When remote sends execute as A then B but their local records arrive as B then A, task A is completed with B's answer and status, so its parent receives the wrong result. laterFollowUp compares local startedAt timestamps, which reflect recording order rather than remote run order; determine which task owns resultRun from the remote run relationship instead.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/server/src/peer/RemoteDelegation.ts around line 743:
When remote sends execute as A then B but their local records arrive as B then A, task A is completed with B's answer and status, so its parent receives the wrong result. `laterFollowUp` compares local `startedAt` timestamps, which reflect recording order rather than remote run order; determine which task owns `resultRun` from the remote run relationship instead.
ApprovabilityVerdict: Needs Human Approval Macroscope's review found this PR requires human approval — Remote follow-ups now become tracked tasks that automatically wake the parent, even after its foreground run ends. This default continuation-policy change requires human confirmation; the reported cross-run result attribution issue also remains separately blocking. Needs human approval because:
Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more. |
Thread transfer impact✅ Thread transfer remains within every enforced ceiling.
Baseline: Scenario and decoded snapshot size10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.
Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed. |
|
No actionable comments were generated in the recent review.
|
| Layer / File(s) | Summary |
|---|---|
Forwarded thread-send callback and wiring apps/server/src/mcp/McpToolAccess.ts, apps/server/src/mcp/toolkits/orchestrator/handlers.ts, apps/server/src/mcp/toolkits/orchestrator/tools.ts, apps/server/src/peer/PeerForwarding.test.ts |
MCP routing can run a callback after forwarding and return its result. t3_thread_send uses this callback to call RemoteDelegation.followUp with the forwarding environment and send result. |
Parent run selection apps/server/src/orchestration-v2/Orchestrator.ts |
followUpOwnerRun selects the highest-ordinal blocking run, or the latest executed run if none is blocking. Remote follow-up requests can use that run when it has a root node. |
Follow-up task recording and result tracking apps/server/src/peer/RemoteDelegation.ts, apps/server/src/peer/RemoteDelegation.test.ts |
RemoteDelegation.followUp records and follows eligible sends to completed remote tasks. Result selection accounts for queued or restarted runs, and stop keys include the remote run ID when available. Tests cover follow-up results, parent completion, and interruption. |
Priority:
Estimated code review effort: 4 (Complex) | ~45 minutes
Change: Bug fix · Severity of issue fixed: Medium
Sequence Diagram(s)
sequenceDiagram
participant MCP caller
participant McpToolAccess
participant t3_thread_send
participant RemoteDelegation
MCP caller->>McpToolAccess: Invoke linked-environment tool
McpToolAccess->>t3_thread_send: Forward thread-send request
t3_thread_send-->>McpToolAccess: Return forwarded send result
McpToolAccess->>RemoteDelegation: Invoke followUp with environment and send result
RemoteDelegation-->>McpToolAccess: Return send result, with task ID when recorded
Possibly related PRs
- pingdotgg/t3code#26344: Adds remote delegated-task request relaying and parent wake handling, which this change extends to remote follow-up sends.
- pingdotgg/t3code#24560: Defines
t3_thread_sendas the operation for same-child follow-ups after the initial delegated task completes. - pingdotgg/t3code#21813: Records later delegated-child wakes as follow-up tasks and delivers their results to the parent.
Suggested reviewers: t3dotgg, macroscopeapp
Merge Risk: 
Minimal · up to 3d2de
-
The follow-up tracking change is mergeable after normal checks; no actionable issue is established.
Pre-merge checks: 
4
-
Full details: Description check
-
Explanation
The description covers the problem, implementation, scope and approval, focused verification results, limitations, and agent usage. It follows the required template and provides specific test evidence.
Full details: Linked Issues check
-
Explanation
Issue
#26740requires a tracked follow-up task and a parent notification whent3_thread_sendcontinues a finished native remote thread.McpToolAccess.afterForwardingroutes the forwarded result toRemoteDelegation.followUp. The new follow-up logic matches the finished remote task by environment and thread, records a newdelegated_task.remote.request, starts its follower, and returns the newtaskId. It usescompletionWake: "always". The message ID supplies idempotent command identity for retries. The run-selection, per-run result, restart, and stop-key changes preserve the original task and support the follow-up lifecycle. The reported tests cover these cases.
Full details: Out of Scope Changes check
-
Explanation
The changes stay within issue
#26740. The forwarding hook is required to process a remote send result. The sharedfollowUpOwnerRunhelper, remote request validation, follower result selection, and run-specific stop key support correct tracking, notification, restart, and cancellation behavior for the same remote follow-up. The added tests cover the affected behavior. No unrelated product area or client change is introduced.
-
Finishing Touches
-
📝 Generate docstrings
-
- Commit to this branch
- Create a new PR
🧪 Generate unit tests (beta)
-
- Commit to this branch
- Create a new PR
-
- Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Comment @coderabbitai help to get the list of available commands.
Problem
When a parent agent follows up on a finished remote delegated task with
t3_thread_send(environmentIdset to the linked environment,threadIdset to the task's remote thread), the second run starts and completes there. But the parent gets notaskId, no task is recorded for that run, and the parent is never woken when it ends. Thet3_thread_senddescription promises "A send to a finished child thread starts a fresh task and returns itstaskId", and local children have done this since https://git.cafe/pingdotgg/t3code/pulls/17696.Reproduced on main
29f33af7d7with the two-environment harness inRemoteDelegation.test.ts: delegate to the linked box, let the task finish, send a follow-up to its thread. The send returnsdelivery: "started"and arunId, with notaskId.Change
The thread in the linked environment has no parent there, so only the parent's environment can record the follow-up.
McpToolAccess.afterForwardinglets a declaration process the result of a call that ran in a linked environment.t3_thread_senduses it to callRemoteDelegation.followUp.followUphandles a send that started or queued a run on the thread of one of the caller's finished remote tasks. It records a new remote task for that run through the existingdelegated_task.remote.requestcommand, starts following it, and returns itstaskId. The command id comes from the send's message id, so a retry with the sameclientRequestIdrecords the task once. Steers, restarts and other threads come back unchanged.followUpOwnerRun, now shared withrecordSubagentFollowup). This includes a caller whose foreground run has ended while a background subagent keeps it live. The remote-request decider accepts that run only for a follow-up on the same remote thread. New delegations still need an active run.followOncegives each task its own run's reply, through restarts, when a later follow-up shares the remote thread.Scope and approval
Fixes #26740
This is a triaged bug. The behaviour is the one the tool description already documents.
Verification
New tests in
apps/server/src/peer/RemoteDelegation.test.ts. All of them fail against main's source:a follow-up sent to a finished task there is a new task that wakes the parent. On main:expected undefined to be definedon the send result'staskId(delivery: "started",runId: "run:box-later").a follow-up there ends with its own reply, not one queued behind ita follow-up there ends with its own reply when the $restarted run is restarted: the follow-up run restarted, and the queued run restarted.a follow-up sent after the parent's own run ended is still a task: the task completes with its own reply and the parent receives a new wake.Two existing Stop tests now expect one stop per task. A follow-up is a task, so Stop reaches its run as well.
Results:
vp test run apps/server/src/peer/RemoteDelegation.test.ts apps/server/src/peer/PeerForwarding.test.ts apps/server/src/mcp/McpToolAccess.test.ts: 108/108 pass.vp run check:changedon the merged head: format, lint, knip, graphql, typecheck (3 packages) and tests (7 files, 124 tests) all pass.No client code changes. The parent's agents panel lists the follow-up as another remote task row, through its existing rendering.
Not checked:
Claude Opus 5.5 (Claude Code) as lead, implementer and test writer, with GPT-6.1-Sol (Codex) and Claude Opus 5.5 as reviewers, run through T3 Code.
🤖 Generated with Claude Code