microsoft / microsoft/vscode

Agent host: client tools execute off the stream-mapper ready rather than the SDK invocation

Open
#330,899 3 comments 0 reactions 1 assignee Claimed by @roblourens View on GitHub
agent-host
Dominant language
TypeScript
Stars
193k
Forks
42.4k
PR merge metrics
PR metrics pending

Description

### Summary

Client tools are executed off the stream-mapper's `chat/toolCallReady`, independently of whether the SDK has invoked that tool through its in-process MCP server. Three separate defects fall out of that single seam, each currently proposed as its own patch.

Filing this per @kycutler's request on #330684, so the root cause can be triaged as one design question rather than three unrelated fixes.

### Mechanism

When a client-contributed tool call reaches `ToolCallStatus.Running`, the host raises a `ToolClientExecution` input request ([agentSideEffects.ts:566-575](https://github.com/microsoft/vscode/blob/main/src/vs/platform/agentHost/node/agentSideEffects.ts#L566-L575)) and the workbench executes the tool. That happens in parallel with, and not ordered against, the SDK's own invocation of the same tool. The ready is emitted by the stream mapper at `content_block_stop` with `confirmed: "not-needed"`.

Because the trigger is the streamed ready rather than the SDK invocation, execution can begin before the arguments are known to be complete, more than once, and before anything is waiting for the result.

### The three defects

| Defect | What goes wrong | PR |
|---|---|---|
| Double invocation | The call is readied twice, by the stream mapper and again by the permission flow, so the tool runs twice | #330683 |
| Empty arguments | The disconnect path synthesises a ready with no `toolInput`, so the tool is invoked with `{}` and throws during preparation | #330684 |
| Dropped result | Execution finishes before the SDK registers its handler, so `respond` discards the result and the turn heartbeats forever | #330730 |

Each is individually reproducible, and each PR carries a test that fails without the fix.

### Why this looks structural

If client tools executed only on the SDK-driven invocation, with the stream-mapper ready used for rendering only, all three become impossible by construction: one authoritative trigger, never without real input, and handler registration ordered before execution.

I did not propose that as a PR because I cannot tell whether the stream-mapper ready is deliberately actionable. Starting a tool as soon as its arguments finish streaming, rather than waiting for the SDK round trip, is a plausible latency win, and the permission flow may already depend on the current ordering.

@kycutler suggested validating arguments against the tool schema, which would catch the undefined page id in #330684 at the boundary rather than in each tool. That seems right for the empty-argument class specifically. It would not by itself address the double invocation or the dropped result, since both are ordering problems rather than payload problems.

### Environment

Reproduced against a dev container (remote agent host) on `1.133.x`, using the Claude provider with auto approvals.

*AI disclosure: this issue was 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.