microsoft / microsoft/vscode

Custom chat-completions model fails intermittently with HTTP/2 GOAWAY (Undici) and truncated request body (Node https fetcher)

Open
#335,219 0 comments 0 reactions 1 assignee Assigned to @ulugbekna View on GitHub
Dominant language
TypeScript
Stars
193k
Forks
42.4k
PR merge metrics
PR metrics pending

Description

When using a custom language model (vendor `customendpoint`, apiType `chat-completions`) in GitHub Copilot Chat, requests fail intermittently at the transport layer with:

```
SocketError: HTTP/2: "GOAWAY" frame received with code 0
at client-h2.js:322 (undici/lib/dispatcher/client-h2.js)
at ClientHttp2Session.onHttp2SessionGoAway
```

Copilot does not retry these failures, so the user sees an error bubble mid-conversation. Retrying the same message usually succeeds, proving the failure is transient and transport-level, not model/auth related.

Additionally, switching to the alternative Node.js `https` fetcher (`github.copilot.advanced.debug.useNodeFetcher: true`) produces a different, deterministic failure: the request body is truncated and the endpoint returns `400 BadRequest - failed to parse request body: unexpected end of JSON input`. So neither available fetcher works reliably with custom chat-completions endpoints.

## Environment

- VS Code: 1.136.2
- GitHub Copilot Chat: 0.64.1 (prod)
- OS: Windows 11 host, VS Code remote WSL2 (Ubuntu 24.04), kernel `6.18.33.2-microsoft-standard-WSL2`
- Custom model: open-weight LLM served via a private OpenAI-compatible inference gateway (chat-completions, toolCalling/vision enabled, contextSize 262144), behind an HTTP/2-terminating proxy/load balancer
- Fetcher settings tested:
- Default (Undici / `useNodeFetchFetcher: true`) → intermittent GOAWAY
- `useNodeFetcher: true` (Node https) → deterministic 400 truncated body
- `useElectronFetcher: true` → not available in WSL remote

## Evidence

### Symptom 1 — GOAWAY with default Undici fetcher

Post-reload reproduction on 2026-09-09:

```text
10:05:47.664 ccreq:1d1bc94d.copilotmd | success | | 1204ms
10:05:53.758 TypeError: fetch failed
SocketError: HTTP/2: "GOAWAY" frame received with code 0
at ClientHttp2Session.onHttp2SessionGoAway (.../undici/lib/dispatcher/client-h2.js:322:31)
10:05:53.758 ccreq:4d7780cd.copilotmd | networkError | | 465ms
```

This occurred after restoring and reloading with the default fetcher configuration:

```jsonc
"github.copilot.advanced.debug.useElectronFetcher": false,
"github.copilot.advanced.debug.useNodeFetchFetcher": true,
"github.copilot.advanced.debug.useNodeFetcher": false
```

Stack trace:

```
SocketError: HTTP/2: "GOAWAY" frame received with code 0
at new SocketError (undici/lib/errors.js)
at ClientHttp2Session.onHttp2SessionGoAway (undici/lib/dispatcher/client-h2.js:322)
```

Observations:

- GOAWAY code is `0` (NO_ERROR) — the gateway is gracefully closing the HTTP/2 session (likely idle timeout / connection recycling), but in-flight streams are aborted.
- Failure is intermittent and mid-stream; a retry of the identical request succeeds.
- One successful call logged: 34,280 input tokens, TTFT 528 ms, total 700 ms — model registration, auth, and context handling are fine.
- The same endpoint/model/network works flawlessly from other clients (e.g., OpenCode), isolating the problem to Copilot's HTTP client stack.
- The nearby `AutomaticInstructionsCollector` mismatch and `enumDescriptions` schema warning are separate warnings. The model call completed successfully despite them; the later failure is explicitly classified as `networkError` and originates in Undici's HTTP/2 GOAWAY handler.

### Symptom 2 — Truncated body with Node https fetcher

With `github.copilot.advanced.debug.useNodeFetcher: true`, every (or nearly every) request fails with:

```
400 inference error: BadRequest - failed to parse request body: unexpected end of JSON input
```

This indicates the Node https fetcher truncates large request bodies (prompts with large context easily exceed 30k tokens). This makes the alternative fetcher unusable as a workaround for symptom 1.

## Expected behavior

1. Copilot should transparently retry requests aborted by a GOAWAY (NO_ERROR) frame, or handle session recycling gracefully.
2. The Node https fetcher should send complete request bodies for large chat-completions payloads.

## Actual behavior

- Undici fetcher: intermittent `SocketError: HTTP/2 GOAWAY code 0`, no retry, user-visible error.
- Node https fetcher: `400 BadRequest - failed to parse request body: unexpected end of JSON input` due to truncated body.

## Repro steps

1. Configure a custom model via `chatLanguageModels.json` with vendor `customendpoint`, apiType `chat-completions`, pointing to an OpenAI-compatible gateway behind an HTTP/2-terminating proxy/gateway.
2. Use the model in Copilot Chat agent mode with large contexts (30k+ tokens).
3. With default fetcher: observe intermittent GOAWAY errors mid-conversation.
4. Set `github.copilot.advanced.debug.useNodeFetcher: true` and reload: observe deterministic `400 ... unexpected end of JSON input`.

## Verified local workaround

Routing only the custom model endpoint through a loopback proxy that uses Node.js `https.request` with `ALPNProtocols: ['http/1.1']` avoids the problematic HTTP/2 session while retaining the `node-fetch` path in Copilot. The gateway was verified to accept HTTP/1.1. This is a workaround rather than a product fix; Copilot should handle graceful GOAWAY without requiring a local proxy.

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.