microsoft / microsoft/vscode

Agent host client tool is cancelled when the derived request collection reads empty for a tick

Open
#331,987 0 comments 0 reactions 1 assignee Claimed by @roblourens View on GitHub
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.