Agent host: a subagent's tool call is never executed and its turn is never closed, so the chat spins forever
- Dominant language
- TypeScript
- Stars
- 193k
- Forks
- 42.4k
- PR merge metrics
- PR metrics pending
Description
In a dev container, a subagent's tool call is announced and then never executed. Nothing runs it, nothing fails it, and nothing times out, so the subagent's turn stays open and the chat spins indefinitely.
I reproduced this six times, on both a patched and a completely stock build, driving a real window through a scripted scenario rather than by hand.
## What happens
A prompt that launches a subagent (`Agent` / `Task`) produces, on the subagent's own chat channel:
```
chat/toolCallStart turn=3fcf1cc0-… Bash
chat/toolCallReady turn=3fcf1cc0-…
(nothing further, ever)
```
At that point, measured inside the container:
- **No process is executing.** `ps` shows no shell, no `find`, no `grep`, nothing but the container's own keep-alive processes.
- **The agent host log is silent.** Its last line is the tool completion that preceded the hang.
- **The UI still reports the turn as running**, and did so for the full 10 minute observation window.
- The parent's spawning `Agent` tool call also never completes.
So the call is announced to the client, never executed by anything, and never reaped.
## It scales with the number of subagents
A prompt that spawns several subagents leaves several turns open. One run, driven the same way:
```
chat/turnStarted x10
chat/turnComplete x4
```
Seven subagent turns were opened and two completed; five were left open, along with the parent's follow-up turn. The chat shows nothing further for any of them and no amount of waiting changes it.
That ratio matches what #332073 reports from a different trigger, which is why I think the missing terminal path is worth treating as its own defect rather than only as a consequence of whatever stalls the tool call.
## Why the turn never closes
`AgentSideEffects.completeSubagentSession` is the only thing that completes a subagent chat's turn, and it is reachable from exactly one place: a `subagent_completed` signal.
```
node/agentSideEffects.ts
590: if (signal.kind === 'subagent_completed') {
591: this.completeSubagentSession(signal.chat.toString(), signal.toolCallId);
```
That signal has two producers, and both are downstream of the spawning tool finishing:
- `claudeMapSessionEvents.ts:406` fires it on the `tool_result` for the spawning `Task`/`Agent` `tool_use`, and only when the spawn is not flagged background.
- `claudeSubagentSignals.ts:129` fires it on a `task_notification` with a terminal status.
Because the spawning tool call never completes, neither producer fires. `subagent_completed` was emitted **zero times in every run**. The subagent turn is therefore never completed, cancelled or errored.
Nothing else closes it. The only thing resembling a watchdog is `agentHostTurnTracker`, which derives a hang reason for telemetry and dispatches no action, so there is no path by which an abandoned turn is ever terminated.
## Reproducing it
Drive a window, send a prompt that spawns a subagent, and wait. The scenario I used, with all prompts read-only:
> Without changing anything, launch a subagent to inventory the top-level directories of this repository, then report what it found.
Then assert that every `chat/turnStarted` eventually reaches a terminal action. The run never satisfies it.
Two measurements make the diagnosis unambiguous and are worth repeating if anyone tries this:
- check `ps` inside the container, to distinguish "the command is slow" from "nothing is running"
- check whether the agent host log is still growing, rather than judging from the UI: the Stop control disappears transiently during tool execution, so the UI is not a reliable indicator of progress
## Scope
Reproduced on **1.135.0** (`08d4889f9ec4a1685d257b9b95de036c8e1ce1e5`), dev container on Docker, agent host in the container, Claude harness, Windows client and Linux container on arm64.
I run a set of local patches against the shipped bundle, so I re-ran this against a **completely stock, reverted bundle** to be sure they were not the cause. It reproduces identically: the turn is still open after 10 minutes with nothing executing. As a control in the same comparison, the stock build produced 2 `ENOPRO` errors where the patched build produced none, which confirms the revert genuinely took effect.
## What I have not established
I have not identified why the announced call is never executed. Two candidates in the existing set look most likely, and I would rather not guess between them:
- [#330899](https://github.com/microsoft/vscode/issues/330899), where a client tool is admitted to execution off the stream-mapper ready rather than the runtime invocation
- [#331595](https://github.com/microsoft/vscode/issues/331595), where a subagent's tool call is handled on the parent chat
Whichever it is, the missing reaper is separable and worth having on its own: a turn whose producing work is gone should not remain open forever, and today nothing can end it but the user.
Related: [#332073](https://github.com/microsoft/vscode/issues/332073) reports the same visible outcome from a different cause (closing the window mid-flight). The grouping is in [#333174](https://github.com/microsoft/vscode/issues/333174).
*AI disclosure: this issue and the related investigation were written with the assistance of AI.*
### Public patches and patcher scripts
[Public patch catalog and patcher scripts](https://github.com/RyanEwen/vscode-patches/blob/main/CATALOG.md) · [Source patch index](https://github.com/RyanEwen/vscode-patches/blob/main/SOURCE-PATCHES.md). The [public collection](https://github.com/RyanEwen/vscode-patches) includes the maintained patchers, rollback instructions, regression scripts, and historical snapshots. Build restrictions and exact installer coverage are documented there.
Contributor guide
Assessment
This issue has not been assessed yet.