{"record":{"id":"a232a594eb0fdd2a","repo":"github/copilot-sdk","slug":"stream-ended-while-reading-json-rpc-headers","errorCode":null,"errorMessage":"Stream ended while reading JSON-RPC headers.","messagePattern":"Stream ended while reading JSON-RPC headers\\.","errorType":"exception","errorClass":"EndOfStreamException","httpStatus":null,"severity":"error","filePath":"dotnet/src/JsonRpc.cs","lineNumber":395,"sourceCode":"        }\n\n        while (headerEnd < 0)\n        {\n            if (filled == buffer.Length)\n            {\n                Array.Resize(ref buffer, buffer.Length * 2);\n            }\n\n            int bytesRead = await _receiveStream.ReadAsync(buffer.AsMemory(filled, buffer.Length - filled), cancellationToken).ConfigureAwait(false);\n            if (bytesRead == 0)\n            {\n                // Clean EOF only if we haven't started a frame; otherwise the peer truncated mid-header.\n                if (filled == 0)\n                {\n                    return (-1, buffer, 0);\n                }\n\n                throw new EndOfStreamException(\"Stream ended while reading JSON-RPC headers.\");\n            }\n\n            filled += bytesRead;\n\n            // Scan for \\r\\n\\r\\n starting from where a match could begin\n            int scanStart = Math.Max(filled - bytesRead - 3, 0);\n            int pos = buffer.AsSpan(scanStart, filled - scanStart).IndexOf(\"\\r\\n\\r\\n\"u8);\n            if (pos >= 0)\n            {\n                headerEnd = scanStart + pos + 4;\n            }\n        }\n\n        // Parse Content-Length. LSP framing puts each header on its own \\r\\n-terminated\n        // line; we walk the lines and require an exact \"Content-Length: \" prefix at the\n        // start of one of them. A substring match anywhere in the header block would\n        // false-positive on values like \"X-Trace: Content-Length: 5\" and desync the stream.\n        // A missing or unparsable Content-Length means the framing is broken — there's","sourceCodeStart":377,"sourceCodeEnd":413,"githubUrl":"https://github.com/github/copilot-sdk/blob/cd8cf15dc3f9e762615790aaed0a771a0f392755/dotnet/src/JsonRpc.cs#L377-L413","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nstream.Write(headerBytes); stream.Close(); // header cut off\n// after\nusing var ms = new MemoryStream();\nms.Write(headerBytes); ms.Write(body);\nms.CopyTo(stream); // single atomic write, then flush/close","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try { await reader.ReadLoopAsync(ct); }\ncatch (EndOfStreamException ex) when (ex.Message.Contains(\"reading JSON-RPC headers\")) {\n    logger.LogWarning(ex, \"Peer truncated a frame header; connection terminated\");\n    await ReconnectAsync(ct);\n}","preventionTips":["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"],"tags":["jsonrpc","framing","stream-truncated"],"backgroundTag":"broken-pipe","analyzedSha":"cd8cf15dc3f9e762615790aaed0a771a0f392755","analyzedAt":"2026-09-09T18:32:31.973Z","contentChangedAt":"2026-09-09T18:32:31.973Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}