{"record":{"id":"42859b17739e38f0","repo":"chenhg5/cc-connect","slug":"read-body-w","errorCode":null,"errorMessage":"read body: %w","messagePattern":"read body: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"agent/copilot/jsonrpc.go","lineNumber":120,"sourceCode":"\t\t\tn, err := strconv.Atoi(strings.TrimSpace(val))\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"invalid Content-Length: %w\", err)\n\t\t\t}\n\t\t\tcontentLength = n\n\t\t}\n\t\t// Ignore other headers (Content-Type, etc.)\n\t}\n\n\tif contentLength < 0 {\n\t\treturn nil, fmt.Errorf(\"missing Content-Length header\")\n\t}\n\tif contentLength > 10*1024*1024 {\n\t\treturn nil, fmt.Errorf(\"Content-Length too large: %d\", contentLength)\n\t}\n\n\tbody := make([]byte, contentLength)\n\tif _, err := io.ReadFull(lr.reader, body); err != nil {\n\t\treturn nil, fmt.Errorf(\"read body: %w\", err)\n\t}\n\treturn body, nil\n}\n\n// rpcClient manages JSON-RPC request IDs and pending responses.\ntype rpcClient struct {\n\twriter *lspWriter\n\tnextID atomic.Int64\n\n\tpendingMu sync.Mutex\n\tpending   map[int64]chan *jsonRPCResponse\n}\n\nfunc newRPCClient(w io.Writer) *rpcClient {\n\tc := &rpcClient{\n\t\twriter:  newLSPWriter(w),\n\t\tpending: make(map[int64]chan *jsonRPCResponse),\n\t}","sourceCodeStart":102,"sourceCodeEnd":138,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/agent/copilot/jsonrpc.go#L102-L138","documentation":"After validating the Content-Length header, readMessage allocates a buffer of exactly contentLength bytes and uses io.ReadFull to read the body. If the stream ends (io.EOF/io.ErrUnexpectedEOF) or any read error occurs before contentLength bytes arrive, the error is wrapped as \"read body: %w\".","triggerScenarios":"The copilot process dies or closes stdout after sending the header but before/within the body; the declared Content-Length is larger than the actual bytes written; the pipe is broken mid-read; a mock stream terminates early.","commonSituations":"Copilot CLI crash or OOM kill mid-message; network-like stream truncation with stdio when the child exits; a test mock that writes a header with length N but only M<N bytes; header says 100 but body contains trailing data missing bytes.","solutions":["Inspect the child process state — check for a crash (see stderr buffer / exit status) that truncated the message","Fix the writer so Content-Length exactly matches the body byte count","Retry the session: truncation usually means the child is gone and the session must be restarted","For mocks, build messages with a helper computing len(body) instead of hard-coded lengths"],"exampleFix":"// before: header/body mismatch truncates the reader\nfmt.Fprintf(w, \"Content-Length: 42\\r\\n\\r\\n%s\", shortBody)\n// after\nfmt.Fprintf(w, \"Content-Length: %d\\r\\n\\r\\n%s\", len(body), body)","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"body, err := readMessage(lr)\nif err != nil {\n    var eofLike = errors.Is(err, io.ErrUnexpectedEOF) || strings.Contains(err.Error(), \"read body\")\n    if eofLike {\n        slog.Warn(\"copilot: message truncated, child likely died — restarting session\")\n        return errSessionDead\n    }\n    return err\n}","preventionTips":["Monitor the child process exit status alongside the reader to distinguish crash vs framing bug","Capture stderr to diagnose truncation causes","Make mock writers compute body length programmatically","Restart the session on truncation; mid-message death is unrecoverable"],"tags":["jsonrpc","io","truncated","stream"],"backgroundTag":"unexpected-eof","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}