Agent host client tool is cancelled when the derived request collection reads empty for a tick
- Dominant language
- TypeScript
- Stars
- 193k
- Forks
- 42.4k
- PR merge metrics
- PR metrics pending
Description
### Summary
An in-flight agent-host client tool is cancelled when the derived input-request collection reads empty for a single tick during a state update. The tool dies as `Canceled: Canceled` or `Tool permission request aborted`, no completion is dispatched, and the agent waits on that tool for the rest of the session.
### Where it happens
`autorunPerKeyedItem` disposes the store of every key absent from the incoming collection:
```js
for (let [p, m] of i)
c.has(p) || (m.store.dispose(), i.delete(p));
```
For agent-host client tools that store owns the request lifecycle, whose disposal reaches
```js
(state?.type === Streaming || state?.type === (targetsConfirmation ? WaitingForConfirmation : Executing) || ...)
&& execution.source.cancel()
```
so disposing the item cancels the shared execution token and kills a tool that is actively running.
### Evidence
Captured on 1.135.0 in a dev container session, using a breakpoint on the `cancel()` call and a logpoint on the disposal loop.
**1. The cancel comes from the keyed-item teardown.** Breakpoint stack at `source.cancel()`:
```
0 (anon) the request-lifecycle disposal
1 dispose
2 dispose
3 Xe
4 clear MutableDisposable.clear
5 dispose DisposableStore.dispose
6 (anon) autorunPerKeyedItem reconciliation
7 Li transaction
```
**2. The collection was empty, not merely changed.** A logpoint on the disposal recorded the dropped key together with the keys still present:
```
DROPPED: toolClientExecution:ahp-chat://default/...:toolu_...
present at that moment (0):
```
Zero siblings. Every key was dropped in that pass, so this is not one request being replaced by another.
**3. The host never withdrew the request.** In the same session there are zero `session/inputNeededRemoved` actions carrying a `toolClientExecution` id. The only removal is the auto-approved `toolConfirmation`. The host still believes the request is outstanding, which is why nothing ever completes it.
**4. It is not specific to one tool.** The same pass also destroyed a `toolConfirmation` belonging to a different tool call.
### Reproduction
Consistent whenever a call receives two `ChatToolCallReady` actions with identical `toolInput` and a different `invocationMessage`, which happens for MCP client tools:
```
"invocationMessage":"Run MCP tool client__issue_fetch"
"invocationMessage":"issue_fetch"
```
Observed counts for each attempt: `toolCallStart: 1`, `toolCallReady: 2`, `toolCallComplete: 0`. Six consecutive calls failed this way, alternating between `Tool permission request failed: AbortError: Tool permission stream closed` and `Tool permission request aborted`.
### What I could not determine
Why the derived collection reads empty. I confirmed the emptiness and its consequence, but not the update that produces it. That seems the right place for a fix, since treating a transient empty read as "every request was withdrawn" is destructive for anything holding live work.
Guarding the reconciliation so an empty incoming collection is not treated as a full teardown removes the symptom locally: the destructive drops go from 2 to 0 on an identical test, and client tools complete again. A returning key reuses its existing item, so in-flight work survives, and the helper's outer cleanup still disposes the stores when the autorun is torn down. Offered as a description of the failure rather than a proposed patch, since the helper is shared.
### Version
- VS Code 1.135.0, commit 110a328ea54b42367b803ec53ee0bf52ef26b419
- Dev container session on Windows 11 ARM64
*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.