github/copilot-sdk · error · EndOfStreamException
Stream ended while reading JSON-RPC headers.
Error message
Stream ended while reading JSON-RPC headers.
What it means
JsonRpc.ReadMessageAsync throws EndOfStreamException when the underlying stream returns zero bytes after at least part of a JSON-RPC header has been read — the peer did not send a clean EOF between frames but truncated mid-header. ReadLoopAsync surfaces this to the connection handler.
Solutions
- Fix the peer to write complete frames atomically (full header+body per write) before closing.
- Treat mid-header truncation as a connection failure: log, close, and reconnect.
- Check the peer's stderr/exit code for a crash that explains the truncated write.
- Ensure no proxy/transport is clipping writes; verify framing uses \r\n\r\n between header and body.
Example fix
// before stream.Write(headerBytes); stream.Close(); // header cut off // after using var ms = new MemoryStream(); ms.Write(headerBytes); ms.Write(body); ms.CopyTo(stream); // single atomic write, then flush/close
Defensive patterns
Strategy: try-catch
Try / catch
try { await reader.ReadLoopAsync(ct); }
catch (EndOfStreamException ex) when (ex.Message.Contains("reading JSON-RPC headers")) {
logger.LogWarning(ex, "Peer truncated a frame header; connection terminated");
await ReconnectAsync(ct);
} Prevention
- Write each JSON-RPC frame in one atomic write
- Check peer process exit codes/stderr for crashes
- Add keep-alive/idle timeouts to detect dead peers quickly
- Avoid mid-frame stream closes in transport code
When it happens
Trigger: Remote process crashes or exits while a frame header is partially written; network/socket dropped mid-header; a writer that closes the stream between a partial header and the rest of the frame.
Common situations: Server or CLI subprocess killed unexpectedly (OOM, ctrl-C); flaky transport dropping mid-message; sending raw bytes that break the Content-Length: ...\r\n framing protocol.
Related errors
- JSON-RPC frame has a missing, duplicate, or invalid…
- JSON-RPC frame is missing the Content-Length header.
- Client not started. Call start() first.
- Expected JSON object for `params` of single-object-param…
- Unexpected end of stream while reading JSON-RPC message
AI-assisted analysis of github/copilot-sdk@cd8cf15dc3 (2026-09-09).
Data as JSON: /api/errors/a232a594eb0fdd2a.
Report an issue: GitHub.
Appendix: source
Thrown at dotnet/src/JsonRpc.cs:395
}
while (headerEnd < 0)
{
if (filled == buffer.Length)
{
Array.Resize(ref buffer, buffer.Length * 2);
}
int bytesRead = await _receiveStream.ReadAsync(buffer.AsMemory(filled, buffer.Length - filled), cancellationToken).ConfigureAwait(false);
if (bytesRead == 0)
{
// Clean EOF only if we haven't started a frame; otherwise the peer truncated mid-header.
if (filled == 0)
{
return (-1, buffer, 0);
}
throw new EndOfStreamException("Stream ended while reading JSON-RPC headers.");
}
filled += bytesRead;
// Scan for \r\n\r\n starting from where a match could begin
int scanStart = Math.Max(filled - bytesRead - 3, 0);
int pos = buffer.AsSpan(scanStart, filled - scanStart).IndexOf("\r\n\r\n"u8);
if (pos >= 0)
{
headerEnd = scanStart + pos + 4;
}
}
// Parse Content-Length. LSP framing puts each header on its own \r\n-terminated
// line; we walk the lines and require an exact "Content-Length: " prefix at the
// start of one of them. A substring match anywhere in the header block would
// false-positive on values like "X-Trace: Content-Length: 5" and desync the stream.
// A missing or unparsable Content-Length means the framing is broken — there'sView on GitHub (pinned to cd8cf15dc3)