Skip to content

fix(orchestrator): Keep Claude child work on the child thread - #5456

Open
mwolson wants to merge 244 commits into
pingdotgg:t3code/codex-turn-mappingfrom
mwolson:fix/claude-subagent-empty-prompt-message
Open

fix(orchestrator): Keep Claude child work on the child thread#5456
mwolson wants to merge 244 commits into
pingdotgg:t3code/codex-turn-mappingfrom
mwolson:fix/claude-subagent-empty-prompt-message

Conversation

@mwolson

@mwolson mwolson commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

What Changed

A Claude child can keep talking after the parent root settles. Later text, tools, and results were landing on the parent thread, so the child's work showed up in the wrong conversation. Those later frames now stay on the child.

A blank or whitespace Agent prompt, or progress that arrived before the real prompt, opened the child with an empty "Sent by another agent" bubble. The child now waits for real prompt text before showing that opening message. A child that never gets a prompt still runs. It just starts with no blank bubble.

Checked on a live Claude thread. One Agent launched with a single space, another with a real prompt. The blank child had no user message. The sibling had exactly one opening message with the real prompt.

Why

Settle on the parent is not the end of that child's stream. Routing later frames with the parent's turn mixed the two conversations.

The empty bubble was the same class of mistake: the child thread showed a user message that nobody sent.

Review both here. #5388 claude-postsettle-attribution is closed as absorbed.

UI Changes

Child output after the parent settles stays in the child thread on web and mobile. A blank Agent launch no longer creates an empty "Sent by another agent" bubble.

No before/after screenshots.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Note

Low Risk
Narrow behavioral change in ACP subagent UI projection with no auth, persistence, or routing logic touched.

Overview
When an ACP provider registers a subagent child thread, the adapter only projects the opening user message and turn item if hasSubagentPromptText(update.prompt) is true (non-empty after trim).

Previously, every subagent launch always emitted message.updated and turn_item.updated for the prompt, which could surface an empty “Sent by another agent” bubble when the agent sent a whitespace-only prompt or progress arrived before real prompt text.

Child thread creation and subagent metadata emission are unchanged; only the optional prompt conversation artifacts are gated.

Reviewed by Cursor Bugbot for commit ed223fe. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Fix Claude child thread output attribution after root thread settlement

  • Adds hasSubagentPromptText to suppress empty opening user message bubbles in child threads when a subagent prompt is blank, whitespace-only, null, or undefined; applied across Claude, ACP, Codex, and Cursor adapters.
  • Reworks session-scoped subagent state in ClaudeAdapterV2 to track subagent lineages per native thread across turns, serializing SDK message handling via a semaphore and using generation-based guards to prevent duplicate emissions.
  • Adds durable alias registration so post-settlement tool results and assistant text with parent_tool_use_id are routed to the correct child thread rather than leaking to the root.
  • Buffers unresolved subagent messages until their lineage alias registers, with overflow protection (>32 pending launches disables implicit alias recovery).
  • Hides provider-buffered automatic continuation messages (text 'Background task completed.') from the visible run queue in deriveThreadQueueWorkflowState.
  • Risk: Large refactor of ClaudeAdapterV2 internal state; subagent indexing moved from per-turn to session scope, which changes how interrupts, session closes, and failed wake drains are handled.

Macroscope summarized ed223fe.

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7340cd12-4409-4cc1-b8b5-91ab73df863b

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels Aug 5, 2026
@juliusmarminge
juliusmarminge force-pushed the t3code/codex-turn-mapping branch from 25de21d to 0af2a6e Compare August 7, 2026 12:10
@mwolson
mwolson force-pushed the fix/claude-subagent-empty-prompt-message branch from 0e1b4fe to 1598863 Compare August 8, 2026 18:43
@macroscopeapp

macroscopeapp Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Skipped

Macroscope did not run approvability analysis for this PR. Diff is too large for automated approval analysis, so this PR cannot be approved automatically.

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

@juliusmarminge
juliusmarminge force-pushed the t3code/codex-turn-mapping branch from 22bd872 to a27c1cc Compare August 10, 2026 17:05
@mwolson
mwolson force-pushed the fix/claude-subagent-empty-prompt-message branch from 1598863 to b5f1b94 Compare August 11, 2026 13:38
@juliusmarminge
juliusmarminge force-pushed the t3code/codex-turn-mapping branch 2 times, most recently from a186d64 to 5b1a115 Compare August 12, 2026 23:19
@mwolson
mwolson force-pushed the fix/claude-subagent-empty-prompt-message branch from b5f1b94 to 9c18ee2 Compare August 13, 2026 21:26
@mwolson mwolson changed the title fix(orchestrator): Skip blank subagent opening messages fix(orchestration): Keep Claude child output attributed after settle Aug 13, 2026
Comment thread apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
@juliusmarminge
juliusmarminge force-pushed the t3code/codex-turn-mapping branch 3 times, most recently from 96891a3 to 993407d Compare August 15, 2026 15:26
juliusmarminge and others added 13 commits August 17, 2026 12:04
Co-authored-by: codex <codex@users.noreply.github.com>
- Initialize provider as unchecked in a pending state
- Update initial probe message to reflect session-local status
- Type the runtime effect with `Scope`
- Build the ACP session runtime without wrapping it in `Effect.scoped`
- Use strict TurnId and ProviderItemId parsing in Codex session routing
- Decode in-memory stdio chunks in streaming mode to avoid split UTF-8 corruption
- Transfer session-owned scopes into adapter state
- Ensure runtime scopes close on stop and startup failure
- Add regression coverage for scoped lifecycle cleanup
- Close the managed native event logger when the adapter layer tears down
- Make session runtime close idempotent with an atomic closed flag
- Add coverage for flushing thread native logs on shutdown
- Use codex app-server snapshots for auth, models, and skills
- Remove legacy CLI/config discovery paths and related helpers
- Update tests for the new provider status flow
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
juliusmarminge and others added 12 commits August 17, 2026 12:10
- Add paginated thread history with bounded snapshots and replay limits
- Trim oversized wire payloads and support progressive mobile history loading
- Add optional title regeneration request and start time to thread shells
- Cover cache serialization on mobile and client runtime
- ChatView: pingdotgg#5880 auto-settle-on-merge setting flows into effectiveSettled,
  pingdotgg#5644 browser favicon project registration effect, activeProjectRef memo,
  desktopByTabId on both RightPanelTabs mounts
- server: provide ServerSecretStore to the McpSessionRegistry's
  ServerEnvironment layer (pingdotgg#6325 reads publish opt-in per descriptor)
- mobile: 3-way merged main's deltas into the v2 thread screens
  (NewTaskDraftScreen keeps the branch title seed + main's environmentId,
  threadListV2 keeps both new test suites, queries imports deduped)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…toggles

Restores main's one-inset rule (pingdotgg#5226) that a rebase resolution had
overridden with a conditional right-2 offset, which made the controls jump
sideways whenever the right panel opened. Also restores the live-agent
count badge on the right-panel toggle (pingdotgg#5745) that the round-6 replay
dropped, and applies the same fixed-position rule to the pull requests
page: the toggle now stays mounted at one absolute inset in both states,
with a footprint spacer in the list header so the refresh button never
slides underneath it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The right controls carry mr-px (main's border compensation for anchoring
inside the panel frame), which left the sidebar trigger one pixel closer
to its edge and the sheet-mode tab bar one pixel tighter than the closed
state. Mirror the pixel on the trigger and the sheet layout-controls slot
so all three read the same inset.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…gger

The trigger's icon falls through to the Button default (size-4) while the
right cluster hard-coded size-3.5, so the two ends of the titlebar read a
pixel apart on every edge. All five layout-control icons now use size-4,
matching the trigger and the pull requests page's refresh icon.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Omit transcript bodies from shell rows
- Query archived threads separately and stream compact resume metadata
Re-applies the deltas that mid-stack blob reverts discarded, and merges
main's work into the v2-owned surfaces:

- keybindings: main's STATIC_KEYBINDING_COMMANDS rename plus both new
  commands (rightPanel.toggleMaximized alongside threadPanel.toggle)
- OpenInPicker: main's remote-open/SSH routing and favorite-editor
  shortcut layered onto the branch's panel/toolbar variants; the
  extracted shouldShowOpenInPicker now takes remoteOpenMode
- ChatMarkdown: main's bare-filename resolver (pingdotgg#6297) ported into the
  branch's module-level component factory, plus pingdotgg#4133 title-attribute
  stripping on links and images
- ComposerPrimaryActions: main's pingdotgg#4781 model (stop stays reachable, send
  joins it when Enter-to-send is unavailable) carrying the branch's
  steering send button
- ComposerPendingUserInputPanel: main's collapsible redesign with the v2
  RuntimeRequestId and responseCapability gate
- ChatComposer: main's oversized-prompt submission guard wrapping the
  branch's dispatch-mode send
- preview shell: main's container-aware width clamp ported into the
  branch's usePreviewPanelInlineSize hook
- MessagesTimeline/Sidebar: main's day-aware timestamps, code-font tool
  bodies and provider accent badges on the v2 runtime shell
- index.css: main's @variant dark migration (pingdotgg#6381) replaces the branch's
  standalone .dark block
- contracts: main's send-turn image mime allowlist re-homed to
  chatAttachment.ts, where v2 keeps the other send-turn limits

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ports v1's pingdotgg#5246 guard into the v2 dispatcher: a stored receipt only
proves that this exact command already ran for the thread it was recorded
against, so returning it for a command aimed at a different thread reports
success for work that never happened there. The check is extracted as
canReplayCommandReceipt so the rule is unit-testable, and reuse now fails
with OrchestratorCommandIdConflictError like the v1 path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Keep prominent activity rows visible with lifecycle status and provider metadata
- Move feed sizing logic into tested helpers and preserve native measurement for activity groups
Re-applies the deltas that mid-stack blob reverts discarded, and merges
main's round-9 work into the v2-owned surfaces:

- settings: main's Integrations page (pingdotgg#7082) coexists with the branch's
  Scheduled Tasks page in the path union, section labels, icons, and
  search catalog
- contracts: main's preview appearance/zoom/viewport settings imports
  restored beside the branch's modelSelection home for ModelSelection
- mobile: main's built-in themes (pingdotgg#6619) re-applied to the v2 thread
  screens and work log (useThemeColor over hand-rolled color-scheme
  ternaries)
- MessagesTimeline: main's pingdotgg#7157 cleanup adopted (toolCallExpandedBody
  class name unexported, implementation-detail test dropped)
- ChangedFilesTree: main's styled tooltip (pingdotgg#7209) carrying the v2 runId
- pullRequestDetail tests: branch's row-action coverage renamed onto
  main's buildAddSelectionToAgentHandoff (pingdotgg#6597)
- lint: migrated the six branch-owned native title tooltips that main's
  new no-native-title-tooltip rule (pingdotgg#7209) flags to styled Tooltips
  (GitActionsControl, QueuedRunsControl, TimelineSystemDivider,
  MessagesTimeline intent badge and MCP tool logo)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ports pingdotgg#7083 into the v2 session path, which replaced the v1
ProviderService where main's gate lives. Instead of withholding the whole
t3-code MCP credential — on this branch it also carries the thread
orchestration and worktree toolkits — the credential is minted without
the "preview" capability when enableAgentBrowserAccess is off, so every
preview tool call rejects while orchestration stays available.

ProviderSessionManager reads the setting at prepare time (deny on an
unreadable settings file, matching main), rotates a reused credential
whose capability set no longer reflects the setting, and the session
config now carries browserToolsAvailable so the Codex adapter keeps its
developer instructions truthful via main's parameterized instruction
builders instead of the removed constants.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@juliusmarminge
juliusmarminge force-pushed the t3code/codex-turn-mapping branch from 519c42a to 4c55679 Compare August 17, 2026 10:28
juliusmarminge and others added 3 commits August 17, 2026 16:29
…s style simplification

Main's pingdotgg#6381 deleted the shared .workspace-topbar and scroll-fade rules
from index.css after inlining them at main's own call sites, but this
branch's slim chat chrome still references both classes. The round-8
rebase took the deletion without migrating the branch call sites, so the
header collapsed to zero height — the breadcrumb sat on the window edge,
timeline rows scrolled unfaded through it, and the thread-details popover
anchored to the collapsed header.

Restores both as composable utilities in pingdotgg#6381's own style: a
workspace-topbar utility for the titlebar rows, and the branch's
chat-timeline-scroll-fade mask (soft ramp plus a full-height scrollbar
column). Also drops the duplicated media override and its dead
settings-page-scroll-fade selector.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ation

A follow-up sweep against pingdotgg#6381 found the branch still carrying the
pre-simplification forms it replaced, which my earlier fix had papered
over with a compat utility instead of finishing the migration:

- ChatView now uses main's inlined titlebar sizing and the
  data-workspace-titlebar-controls hook on both control clusters. The
  class-based markup was silently missing the themed-toggle bridge
  (html[data-theme-id] [data-workspace-titlebar-controls] …), so custom
  themes lost their titlebar accent in the thread view.
- The scroll-to-end pill becomes main's Button size="xs" variant="glass"
  instead of a hand-rolled button recreating it.
- MessagesTimeline uses main's consolidated topbar-scroll-fade utility;
  the byte-identical chat-timeline-scroll-fade copy and the
  workspace-topbar compat utility are gone.
- The composer-glass dark rules move into nested @variant dark like
  main's (the raw .dark duplicates could drift from the nested copies
  they shadowed), including the branch-only queue strip.
- The pre-pingdotgg#6381 dialog-glass/dialog-backdrop/dropdown-glass class rules
  and their .dark variants are deleted: the pingdotgg#6381 utilities plus
  call-site shadow utilities own every declaration, and the stale
  dropdown rule still had the saturate-less backdrop-filter. The dead
  model-picker-surface dark rule goes with them.

index.css now has zero raw .dark selectors outside the variant
definitions, matching the doctrine in
.macroscope/check-run-agents/ui-consistency.md. Verified against the
emitted production CSS: dark variants compile to :is(.dark,.dark *) with
their @supports color-mix fallbacks intact.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Complete retry items when provider activity resumes
- Keep retry progress visible across web and mobile clients
@mwolson
mwolson force-pushed the fix/claude-subagent-empty-prompt-message branch from 9c18ee2 to ea1bb95 Compare August 19, 2026 15:37
CI Check fails on current CTM because layerTest now surfaces
ServerSettingsError while the ProviderSessionManager test layer requires
never. orDie matches the other test layers.
A provider-native subagent could open its child thread with an empty or
whitespace-only user message, which renders as an empty bubble under the
"Sent by another agent" label. The Claude adapter emitted the opening
prompt whenever the subagent was new, and a task_progress frame can
register a subagent before any frame carries its prompt, so the message
was emitted with the "" default and never rewritten when the real prompt
arrived on a later task_started.

A shared hasSubagentPromptText guard now gates every subagent opening
message. The Claude adapter emits it the first time the task prompt
actually has text rather than on first registration, so a late prompt
still opens the child thread exactly once. Codex's existing length check
becomes trim-aware, and Cursor and ACP pick up the same guard.
@mwolson
mwolson force-pushed the fix/claude-subagent-empty-prompt-message branch from ea1bb95 to 4c2c69c Compare August 19, 2026 18:05
Comment thread apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
Comment thread apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts Outdated

@cursor cursor Bot left a comment

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.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 68422a1. Configure here.

Comment thread apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
@t3dotgg t3dotgg added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. and removed vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Aug 24, 2026
@mwolson mwolson changed the title fix(orchestration): Keep Claude child output attributed after settle fix(orchestrator): Keep Claude child work on the child thread Aug 27, 2026
@juliusmarminge
juliusmarminge force-pushed the t3code/codex-turn-mapping branch from 2d623ac to 37f40d9 Compare August 28, 2026 03:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL 1,000+ 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.

7 participants