Skip to content

fix(server): a follow-up to a finished remote task is a new task that wakes the parent - #26776

Open
juliusmarminge wants to merge 5 commits into
mainfrom
fix/issue-26740
Open

juliusmarminge wants to merge 5 commits into
mainfrom
fix/issue-26740

Conversation

@juliusmarminge

Copy link
Copy Markdown
Member

Problem

When a parent agent follows up on a finished remote delegated task with t3_thread_send (environmentId set to the linked environment, threadId set to the task's remote thread), the second run starts and completes there. But the parent gets no taskId, no task is recorded for that run, and the parent is never woken when it ends. The t3_thread_send description promises "A send to a finished child thread starts a fresh task and returns its taskId", and local children have done this since https://git.cafe/pingdotgg/t3code/pulls/17696.

Reproduced on main 29f33af7d7 with the two-environment harness in RemoteDelegation.test.ts: delegate to the linked box, let the task finish, send a follow-up to its thread. The send returns delivery: "started" and a runId, with no taskId.

Change

The thread in the linked environment has no parent there, so only the parent's environment can record the follow-up.

  • McpToolAccess.afterForwarding lets a declaration process the result of a call that ran in a linked environment. t3_thread_send uses it to call RemoteDelegation.followUp.
  • followUp handles 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 existing delegated_task.remote.request command, starts following it, and returns its taskId. The command id comes from the send's message id, so a retry with the same clientRequestId records the task once. Steers, restarts and other threads come back unchanged.
  • The follow-up binds to the same parent run a local follow-up does (followUpOwnerRun, now shared with recordSubagentFollowup). 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.
  • followOnce gives each task its own run's reply, through restarts, when a later follow-up shares the remote thread.
  • Each task's stop key there now includes its run, so a follow-up's Stop isn't deduplicated against the original task's.

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 defined on the send result's taskId (delivery: "started", runId: "run:box-later").
  • a follow-up there ends with its own reply, not one queued behind it
  • a 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:changed on 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:

  • A live run with two linked servers and real provider agents. The tests use a simulated remote side.
  • Captures of the follow-up row in the web and mobile agents panels.

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

juliusmarminge and others added 5 commits October 29, 2026 00:30
…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>
@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Oct 29, 2026
other.remoteChild?.environmentId === remote.environmentId &&
other.remoteChild.threadId === remote.threadId &&
// One recorded in the same instant may be later too.
(recordedAt === null ||

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 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.

@macroscopeapp

macroscopeapp Bot commented Oct 29, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: 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:

  • 1 blocking correctness issue found at or above your repo's Minimum Blocking Severity

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

@github-actions

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire 5.0 KiB 5.0 KiB 0 B (0.0%) 6.8 KiB ✅
Codex Thread snapshot wire 3.8 KiB 3.8 KiB 0 B (0.0%) 4.9 KiB ✅
Codex Live turn WebSocket wire 1.3 KiB 1.3 KiB 0 B (0.0%) 2.0 KiB ✅
Codex Live turn WebSocket decoded 21.6 KiB 21.6 KiB 0 B (0.0%) 24.4 KiB ✅
Codex Live turn messages 2 2 0 (0.0%) 8 ✅
Claude Total thread wire 5.0 KiB 5.0 KiB 0 B (0.0%) 6.8 KiB ✅
Claude Thread snapshot wire 3.8 KiB 3.8 KiB 0 B (0.0%) 4.9 KiB ✅
Claude Live turn WebSocket wire 1.3 KiB 1.3 KiB 0 B (0.0%) 2.0 KiB ✅
Claude Live turn WebSocket decoded 21.9 KiB 21.9 KiB 0 B (0.0%) 24.4 KiB ✅
Claude Live turn messages 2 2 0 (0.0%) 8 ✅

Baseline: 7b87466 · PR result: 3d2de18 · Source CI: success

Scenario and decoded snapshot size

10 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.

  • Codex decoded thread snapshot: 108.5 KiB
  • Claude decoded thread snapshot: 108.8 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@coderabbitai

coderabbitai Bot commented Oct 29, 2026

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between 7b87466 and 3d2de18.

Files selected for processing: 7
  • 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/orchestration-v2/Orchestrator.ts
  • apps/server/src/peer/PeerForwarding.test.ts
  • apps/server/src/peer/RemoteDelegation.test.ts
  • apps/server/src/peer/RemoteDelegation.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 6 remain after this review.

Walkthrough

The change adds post-forwarding handling for t3_thread_send. Sends to completed remote tasks can create a separate tracked follow-up task, which is associated with a parent run and followed for its result.

Changes: Remote thread follow-ups

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: Normal

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
Loading

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_send as 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: Passed 4
Check name Status Explanation
Title check Passed The title clearly and concisely describes the main fix: follow-ups to finished remote tasks create a new task and wake the parent.
Description check Passed 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…
Linked Issues check Passed Issue #26740 requires a tracked follow-up task and a parent notification when t3_thread_send continues a finished native remote thread. McpToolAccess.afterForwarding routes the forwarded result to…
Out of Scope Changes check Passed The changes stay within issue #26740. The forwarding hook is required to process a remote send result. The shared followUpOwnerRun helper, remote request validation, follower result selection, and r…
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 #26740 requires a tracked follow-up task and a parent notification when t3_thread_send continues a finished native remote thread. McpToolAccess.afterForwarding routes the forwarded result to RemoteDelegation.followUp. The new follow-up logic matches the finished remote task by environment and thread, records a new delegated_task.remote.request, starts its follower, and returns the new taskId. It uses completionWake: "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 shared followUpOwnerRun helper, 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.

@juliusmarminge juliusmarminge added bugfix Fix PR opened by an issue lead; enters the PR loop pr:needs-julius Needs Julius: route gate, unverifiable surface, or blocked labels Oct 30, 2026

This branch has not been deployed

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

Labels

bugfix Fix PR opened by an issue lead; enters the PR loop pr:needs-julius Needs Julius: route gate, unverifiable surface, or blocked size:L 100-499 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Resuming a completed remote delegate does not create a follow-up task or notify its parent

1 participant