anthropics / anthropics/claude-code
[BUG] SendMessage to a LIVE Workflow agent resumes a second copy from its transcript ("Resuming agent") while the original keeps running inside the workflow
- Langage dominant
- Python
- Étoiles
- 145k
- Forks
- 23.1k
- Métriques de merge des PR
- Métriques de PR en attente
Description
## 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: }`, the tool result says `Resuming agent ` 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= 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.
Guide de contribution
Aucun guide de contribution indexé pour ce dépôt
Piste de recherche
Reproduce the supplied prompt using the Workflow and SendMessage entry points, then trace workflow-agent registration and the resume path that creates the local_agent task. Done means messaging a live workflow agent no longer starts a second copy: it is refused or queued, while the original remains the sole running instance.
Rédigé par le modèle d'indexation à partir du texte de l'issue.
Évaluation
- Domaine
- ai-infra-agents, devtools
- Type d'issue
- Bug
- Difficulté
- 4/5
- Temps estimé
- 3-5 jours
- Activité
- Active
- Clarté
- Plutôt claire
- Accessibilité débutants
- 48/100