Agent host: responses render only at turn end because the optimistic turn start is never retired
- Dominant language
- TypeScript
- Stars
- 193k
- Forks
- 42.4k
- PR merge metrics
- PR metrics pending
Description
Enabling the agent-host debug logging settings stops the chat rendering incrementally. The response still arrives at the same speed, but nothing is painted until the turn finishes, at which point the entire reply appears at once. Two separate settings each cause it on their own.
This is easy to miss because the settings are exactly what you turn on to investigate a chat problem, and turning them on changes the behaviour you are trying to observe.
## Steps to reproduce
1. In a Claude agent-host session, send a prompt that produces a long, pure-text answer with no tool calls, for example "Without using any tools at all, write a numbered list from 1 to 60 where each line is the number followed by one sentence about database indexing."
2. Watch the response render.
3. Set `chat.agentHost.ahpJsonlLoggingEnabled` to `true`, reload the window, and send the same prompt.
**Expected:** the text streams in continuously in both cases.
**Actual:** with the setting on, the chat shows only the thinking indicator for the whole turn, then the complete answer appears in a single update.
## Measurements
Sampled once per second by reading the length of the last `.interactive-response` element and counting distinct values, so each "update" is a visible change on screen.
Fresh profile (empty `user-data-dir`), identical build, same dev container and agent host:
| Settings | Distinct updates in a ~35s response |
|---|---|
| none | 25 |
| `chat.agentHost.ahpJsonlLoggingEnabled` | 3 |
| `chat.agentHost.agentDebugLog.enabled` | 2 |
Existing profile, same build:
| Settings | Distinct updates |
|---|---|
| all three logging settings on | 2 |
| all three off | 37 |
The third setting is `github.copilot.chat.agentDebugLog.fileLogging.enabled`. Turning off only the two `chat.agentHost.*` ones was not sufficient in the existing profile; all three had to be off.
## The host is streaming normally
With `AgentHostStateManager`'s envelope logging temporarily raised to `info`, a failing turn shows the host emitting **100 `chat/delta` envelopes**, one or two every second, evenly across the turn, and `ProtocolServer` broadcasting each one. The frames are produced and sent correctly. Only the client rendering collapses into a single paint.
## Why this does not look like ordinary slowness
Turn duration is unchanged: about 31 seconds with logging on and about 35 seconds with it off, for comparable output sizes. Work that merely competed for the thread would make the turn slower and produce fewer, laggier paints. Instead the turn takes the same time and produces no intermediate paints at all.
## Notes toward a cause, not a diagnosis
I could not pin the mechanism, so this is offered as a lead rather than a conclusion.
Each received frame pays synchronous work before dispatch, in `webSocketTransport`:
```ts
this._ahpLogger?.log(message, 'c2s', getAhpLogByteLength(text));
this._onMessage.fire(message);
```
and inside `AhpJsonlLogger.log`:
```ts
const entry = { ...message, _ahpLog: meta };
let body = stringifyAhpLogEntry(entry);
this._pending.push(VSBuffer.fromString(line));
```
That is a clone, a full `JSON.stringify`, and two complete UTF-8 encodes per frame, since `getAhpLogByteLength(text)` re-encodes the raw text only to count its bytes while `log()` encodes the serialized line again. The double encode looks worth removing regardless of this bug.
However, the disk writes themselves are queued and non-blocking, so `log()` should not delay `_onMessage.fire()`, and the arithmetic does not obviously support saturation: the clearest failure is the light case, roughly 3 frames per second, where a few passes over a small payload should cost nothing. The fact that a second, independent setting (`chat.agentHost.agentDebugLog.enabled`) reproduces it suggests a shared effect of attaching any per-frame observer to the stream, rather than the cost of the logging itself.
## Version
Visual Studio Code 1.1.3 (stable), server commit `110a328ea54b42367b803ec53ee0bf52ef26b419`, dev container on linux-arm64, Windows 11 host.
Disclosure about the binary: this install carries local modifications to `workbench.desktop.main.js` and `agentHostMain.js`. The comparisons above hold the binary constant and vary only settings, and the effect also reproduces in a fresh profile, so the modifications are not implicated. Say if a clean-install reproduction would help and I will produce one.
*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.