Copilot agent request can hang when WebSocket emits `error` without `close`
- Dominant language
- TypeScript
- Stars
- 193k
- Forks
- 42.4k
- PR merge metrics
- PR metrics pending
Description
- Copilot Chat Extension Version: 0.61.0
- VS Code Version: 1.133.0
- OS Version: Windows 11 Enterprise, build 26200
- Feature: Agent mode
- Selected model: Not captured in the sanitized occurrence record
- Logs: Sanitized signature and state transition below; no prompts or responses included
## Steps to Reproduce
1. Start a long-running Copilot agent request using a model whose metadata enables the Responses API WebSocket endpoint.
2. While an active request is awaiting `handle.firstEvent` / `handle.done`, cause the Electron WebSocket to emit an `error` event without a subsequent `close` event. The linked PR's unit test reproduces this deterministically with `FakeWebSocket.simulateError()` and no close.
3. Observe that the agent remains pending until the user presses Stop or Retry.
## Expected
An active-socket error should settle the request immediately. The existing request-error path can then retry over HTTP when eligible, or show a terminal error to the user.
## Actual
`ChatWebSocketConnection` records the error in `_pendingErrorMessage`, but only calls `handleConnectionClose` from the socket `close` listener. If no close event follows, both `firstEvent` and `done` remain pending, so the request has no terminal transition and HTTP fallback never starts.
Observed log signature:
```text
[ChatWebSocketManager] Error for conversation turn : :
```
No matching later line was present for the same conversation and turn:
```text
[ChatWebSocketManager] Connection closed for conversation turn ...
```
After manual intervention, persisted state showed:
```text
native status: idle (1)
lastResponseState: 2
transport failure: chat-websocket-error
```
This occurred in five conversations in one extension host on the same day. One affected conversation reproduced the signature again while the investigation was running. Tool executions had completed; the silent interval began after a tool/sub-turn, not inside a terminal command.
## Root Cause
In `extensions/copilot/src/platform/networking/node/chatWebSocketManager.ts`, the active-socket `error` handler only stores `_pendingErrorMessage`. Request settlement occurs in the independent `close` handler. The implementation assumes every error is followed by close, which is not guaranteed by the observed Electron transport behavior.
## Proposed Fix
On an active-socket error:
1. Mark the connection closed.
2. Clear the active request from the connection.
3. Settle it through `handleConnectionClose(1006, '', errorMessage)`.
4. Close the underlying socket.
The linked PR's regression emits `error` without `close` and verifies `firstEvent` and `done` reject and the connection closes.
## Validation
- Test-only patch at `2e0b8aad459cc615790f9510ea8fe480ae034333`: the new regression timed out after 5000 ms, confirming that the request remained pending.
- Implementation patch applied: all 24 tests in `chatWebSocketManager.spec.ts` passed.
- `npm run typecheck`: passed.
- `npm run lint`: passed with zero warnings.
- TypeScript language diagnostics and `git diff --check`: clean.
Potentially related but not duplicate: #314336 and #329550. Neither public report identifies the error-without-close state-machine path.
Contributor guide
Assessment
This issue has not been assessed yet.