fix(server): foreground Claude forks no longer show as finished the moment they start#26459

merged2
main
GitHub fork: t3code/claude-fork-placeholder-result
opened5d agobyHhenrychu04
View on GitHubTwo-way sync enabledPR last imported 1d ago

Problem

When Claude starts a fork subagent in the foreground (Agent with subagent_type: "fork" and run_in_background: false), T3 marks the subagent completed about a second after it starts, with the result "Fork started — that fork is you." The subagent row and child thread then say "Completed in 1.0s" while the fork keeps working, and the child thread's first reply is that placeholder text. The real result replaces it only when the fork ends.

Cause: Claude Code opens a foreground fork's stream with a placeholder result for the fork's own launch call. It streams that message like any subagent's opening prompt, tagged with parent_tool_use_id set to the launch call itself. ClaudeAdapterV2 reads every tool result for an Agent call as that subagent's final result, unless it is a background launch acknowledgement. The placeholder is not one of those, so the adapter settles the subagent with it.

Background forks are unaffected: Claude Code does not stream the placeholder for them.

Change

One guard in the tool-result loop: skip a tool result whose tool_use_id equals the message's own parent_tool_use_id. A subagent's stream can never carry the real result of its own launch call, which always arrives on the stream that made the call. So the check matches only the placeholder and depends on no wording. The fork still settles through task_notification or the hand-back result, as before.

Scope and approval

A very small, focused fix for an obvious bug: a running subagent is reported as completed with placeholder text as its result. One condition in the adapter plus one focused test, with no contract, client, or product-behavior change.

Verification

Environment: Claude Code 2.1.312 (sdk-ts and claude -p stream-json), macOS.

Reproducing the frame. I captured the raw stream with claude -p --input-format stream-json --output-format stream-json --verbose --include-partial-messages --model opus --allowedTools Agent. The prompt asked for one fork, run in the foreground, replying FORK-DONE. No environment overrides were set, and the model sent a boolean run_in_background: false. Relevant frames, IDs shortened:

113 assistant parent=null tool_use Agent id=…hMvq2M2T {"subagent_type":"fork","run_in_background":false}
215 system/task_started tool_use_id=…hMvq2M2T subagent_type=fork is_backgrounded=false
316 user parent=…hMvq2M2T tool_result for=…hMvq2M2T "Fork started — that fork is you."
420 system/task_notification tool_use_id=…hMvq2M2T status=completed summary="FORK-DONE"
521 user parent=null tool_result for=…hMvq2M2T "[Subagent hand-back] …"

Frame 16 is the placeholder: the fork's own launch call, answered inside the fork's own stream. The same run with run_in_background left out (background) never streams it.

Adapter replay of the full captured session. I fed every frame of that session, in order, through ClaudeAdapterV2 using the wake harness (throwaway test, not committed):

after frame 15after frame 16after frame 20
mainrunningcompleted: "Fork started — that fork is you."completed: "FORK-DONE"
this PRrunningrunningcompleted: "FORK-DONE"

No real results skipped. Across the 18 recorded Claude fixtures in testkit/fixtures (53 tool results, 17 inside subagent streams, including foreground nested subagents), no tool result answers its own stream's launch call.

Focused test. I added keeps a foreground fork running past the placeholder result for its own launch to ClaudeAdapterV2.test.ts. It sends the captured placeholder frame (reduced to the fields the adapter reads) between the file's existing task-started and task-notification helpers. On main it fails with expected 'Fork started — that fork is you.' to equal 'FORK-DONE'; with the fix it passes.

  • vp test run apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts: 245 passed.
  • vp run check:changed: format, lint, typecheck (36 packages), and knip 2/2 pass. Knip 1/2 flags one local untracked file outside this PR. The test step ran 1702 files: 5 unrelated files fail on my machine. Four of them (DesktopLinuxUrlHandler, externalLauncher, CodexInstallation, build-desktop-artifact) fail the same way on clean main. The fifth is the web browser test GuestPage (scroll follow), which a server-only change does not reach.

Not checked: I did not re-run a live fork in the desktop or mobile client with this build. The fix is server-only, and the replay above uses the real captured frames.

Written with Claude Opus 5.5 in Claude Code.

🤖 Generated with Claude Code

Checks

21 successful · 15 skipped
All checks passed
b6dcb8a

Added 1 commit

  • fix(server): foreground Claude forks no longer show as finished the moment they start4d8f783
Synced from GitHub

Approvability

Verdict: Approved at 4d8f783

Macroscope's review found this PR approvable — This prevents a foreground Claude fork's launch placeholder from marking it completed while it is still running. Existing completion paths remain unchanged, and the reported captured-stream replay supports the narrowly scoped behavior change.

You can add or adjust custom eligibility rules. Learn more.

Synced from GitHub

Review in Change Stack →

No actionable comments were generated in the recent review.

External image Recent review info
External image Run configuration
External image Commits

Reviewing files that changed from the base of the PR and between 4d8f7831db9382f842107c214b3676952da47385 and b6dcb8aa7194244a66378f9b3d8aa9070cf7188a.

External image Files selected for processing: 1
  • apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts

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

Walkthrough

ClaudeAdapterV2 now skips a foreground fork’s placeholder tool result when its ID matches the enclosing message’s parent tool-use ID. A regression test checks that the fork completes with the result from its task notification.

Changes: Foreground fork completion

Layer / File(s)Summary
Placeholder result filtering and completion test
apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts, apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts
ClaudeAdapterV2 skips a placeholder result when its tool-use ID matches the message’s parent tool-use ID. The test checks that the fork’s settled result is FORK-DONE after its task notification.

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix

Possibly related issues

  • pingdotgg/t3code#16610: The issue describes a Claude subagent marked completed by an interim result while it continues running. This change skips a foreground fork’s matching launch placeholder result.

Suggested reviewers: juliusmarminge

Merge Risk: External image Minimal · up to b6dcb

No merge-blocking issue is established by the reviewed change.

Pre-merge checks: Passed 4
Check nameStatusExplanation
Title checkPassedThe title names the primary change: foreground Claude fork subagents no longer report as finished at launch. The change adds a guard in ClaudeAdapterV2 that skips the placeholder tool result matching …
Description checkPassedThe description follows the repository template. It has Problem, Change, Scope and approval, and Verification sections. The Problem section gives the expected behavior, reproduction frames, and root c…
Linked Issues checkPassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes checkPassedCheck skipped because no linked issues were found for this pull request.
Full details: Title check

Explanation

The title names the primary change: foreground Claude fork subagents no longer report as finished at launch. The change adds a guard in ClaudeAdapterV2 that skips the placeholder tool result matching the fork's own parent_tool_use_id, and adds a regression test. The title uses the conventional commit format and is specific, so it matches the changeset.

Full details: Description check

Explanation

The description follows the repository template. It has Problem, Change, Scope and approval, and Verification sections. The Problem section gives the expected behavior, reproduction frames, and root cause. The Change section explains the one-line guard and why it matches only the placeholder. The Scope section explains why the fix is small and needs no prior issue. It gives the verification environment, the focused test, the results on main and with the fix, the broader test run with its unrelated failures, and what was not checked. One gap: the Scope section does not link a triaged issue or maintainer approval. It argues the fix qualifies as a very small, focused fix of an obvious bug, which the template allows. The template also asks for the model and harness, and the author included that line.

Finishing Touches
Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Added 1 commit

  • test(server): build the foreground fork case from the shared frame helpersb6dcb8a

Note

Written by gpt-6.1-sol on behalf of Maria

Verified in a real dev environment

verified as-is at b6dcb8aa7194244a66378f9b3d8aa9070cf7188a on a disposable linux vm, using the dev web client in google chrome through the repo's playwright dependency. created the project and threads through the app against a real git repo. used real claude code 2.1.316 / opus 5.5 turns through the local provider proxy, with no mocks or replay data.

reproduced first on github/main at 5732bbee39: a native Agent call with subagent_type: "fork" and run_in_background: false ran a 45-second node command. the child immediately displayed the launch placeholder and "completed in 1s" while its parent and command were still running.

stopped main's server, installed the exact pr head, and repeated the same foreground fork scenario. the child stayed working through the delay, displayed no placeholder reply, then completed with FORK-DONE after 49 seconds. the parent received the final result. confirmed the native foreground/background arguments in the provider session logs.

neighboring paths also passed: a native background fork completed with BACKGROUND-DONE and woke its parent; the completed child and result survived a browser reload. codex gpt-6.1-sol completed an ordinary turn, resumed the same thread, ran git status --short, and returned CODEX-RESUME-OK.

short runtime transcript:

text
1main server/web: 16733 / 8693
2main foreground child: completed in 1s; "Fork started — that fork is you."
3main parent: remained working until the real fork finished
4main server stopped before starting pr server
5pr server/web: 13773 / 5733
6pr foreground child: working at 38s; FORK-DONE; completed in 49s
7pr background child: BACKGROUND-DONE; completed in 16s; parent wake delivered
8pr codex: CODEX-CHECK-OK; shell tool; CODEX-RESUME-OK
9blacksmith: vp test run apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts
10245 passed

the head's 28 reported ci checks were successful when inspected. no source changes or pushes were needed.

main: premature completion and placeholder while the parent is running

pr: foreground fork still working during the same 45-second command

pr: real result and completion after 49 seconds

pr: foreground fork interaction and completion, 4x playback

Not verified

native desktop, ios/android, macos/windows, relay/tunnel connections, other providers, and nested foreground forks were not exercised. this pass used the linux web client and local proxy. the focused adapter suite was rerun on blacksmith; a fresh full ci suite was not run. the video was inspected and its uploaded playback and seeking checked in t3's browser.