[BUG] SendMessage to a LIVE Workflow agent resumes a second copy from its transcript ("Resuming agent") while the original keeps running inside the workflow

Status Open
Reported on v2.1.269
Maintainer reply None cached
Activity 1 comment · opened Sep 12, 2026

Summary

Agents spawned by the Workflow tool (dynamic workflows, agent() in the script) are not addressable while they run. If such an agent sends SendMessage to main and the main thread replies with SendMessage {to: <that agent id>}, the tool result says Resuming agent <id> and a second, independent copy of that agent is started from its persisted transcript (system/task_started with task_type: "local_agent" and task_id equal to the agent id) while the original copy is still executing inside the workflow. From then on two writers work on the same task and the same files.

Related but different mechanism: #91353 (background Agent tool + resume after a stalled completion). Here the original agent has not stalled or completed; it is simply an in-process workflow agent, and the resume path does not see it as live.

Environment

  • Claude Code 2.1.269 (macOS arm64), --output-format stream-json --verbose
  • Reproduces in -p mode with --dangerously-skip-permissions; first seen in an interactive session where four lane agents of a workflow phase were each messaged once and each got duplicated (duplicate code blocks, conflicting edits, broken builds).

Deterministic reproduction

Prompt (model haiku is enough):

ultracode

Step 1: call the Workflow tool exactly once with the script below, verbatim.
Step 2: a message from the workflow agent will arrive; it contains its agent id. As soon as it arrives, call SendMessage with `to` set to that exact agent id and message "pong". Report the full tool result text of that SendMessage call in your reply.
Step 3: wait for the workflow to complete (TaskOutput on its task id, block true, timeout 120000), then reply with the SendMessage result text followed by the word DONE.

export const meta = {
  name: 'dup-probe',
  description: 'Probe messaging a live workflow agent',
  phases: [{ title: 'Run' }],
}
phase('Run')
const r = await agent('First, call SendMessage with to: "main" and message: "ping from agent id " followed by your own agent id (it appears in your context; if you cannot find it, use the word unknown). Then run the shell command `sleep 60` with Bash. Then reply with exactly the word ok.', {label: 'slowpoke', model: 'haiku', effort: 'low'})
return { r }

Observed on stdout (stream-json, abbreviated):

system/task_started   task_type=local_workflow  workflow_name=dup-probe  tool_use_id=toolu_017L…
system/task_started   task_type=local_bash  owned_by_subagent=true  description="Sleep for 60 seconds"   <- the workflow agent is live and running its sleep
assistant tool_use    SendMessage {"to":"a55b7012793deae02","message":"pong"}
system/task_started   task_type=local_agent  task_id=a55b7012793deae02  description="slowpoke"  subagent_type=general-purpose  prompt="pong"   <- SECOND copy
user tool_result      {"success":true,"message":"Resuming agent a55b701","resumedAgentId":"a55b7012793deae02", …}
system/task_notification task_id=b7n2… status=stopped                       <- the original's sleep gets stopped
system/task_notification task_id=<workflow> status=completed
system/task_notification task_id=a55b7012793deae02 status=completed  summary="Acknowledged. The coordinator responded to the ping. ok"

Note that the workflow agent never emitted a task_started of its own; its only trace is the workflow_agent entry in task_progress.workflow_progress. The resume logic apparently only consults the task registry, does not find a live task for that id, and takes the "agent-stopped → resume from transcript" branch.

Expected

One of:

  • SendMessage to an agent that is currently running inside a Workflow is refused (or queued for that agent's next tool round, like the in_process branch does for ordinary live subagents), with a tool result that says so.
  • Or the workflow's agents are registered as live tasks so the existing "already waking; message queued" path applies.

A flag or setting that refuses the resume of any live agent would also be fine; we would adopt it.

Impact

In the real session each of four parallel lane agents (one per platform) got a duplicate writer editing the same files: duplicated blocks in a Kotlin view model that stopped compiling, two conflicting API shapes in Rust files, a TypeScript file rewritten twice, and the duplicates kept editing after the workflow phase had finished and the next phase's agents had started, causing a second collision. Nothing in the transcript flags that the same agent id now has two instances.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗