{"record":{"id":"0234f5bf6bcdd5c0","repo":"chenhg5/cc-connect","slug":"write-body-w","errorCode":null,"errorMessage":"write body: %w","messagePattern":"write body: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"agent/copilot/jsonrpc.go","lineNumber":73,"sourceCode":"\treturn &lspWriter{w: w}\n}\n\nfunc (lw *lspWriter) writeMessage(v any) error {\n\tdata, err := json.Marshal(v)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"marshal: %w\", err)\n\t}\n\n\theader := fmt.Sprintf(\"Content-Length: %d\\r\\n\\r\\n\", len(data))\n\n\tlw.mu.Lock()\n\tdefer lw.mu.Unlock()\n\n\tif _, err := io.WriteString(lw.w, header); err != nil {\n\t\treturn fmt.Errorf(\"write header: %w\", err)\n\t}\n\tif _, err := lw.w.Write(data); err != nil {\n\t\treturn fmt.Errorf(\"write body: %w\", err)\n\t}\n\treturn nil\n}\n\n// lspReader reads Content-Length framed JSON-RPC messages.\ntype lspReader struct {\n\treader *bufio.Reader\n}\n\nfunc newLSPReader(r io.Reader) *lspReader {\n\treturn &lspReader{reader: bufio.NewReaderSize(r, 64*1024)}\n}\n\n// readMessage reads one Content-Length framed message and returns the raw JSON body.\nfunc (lr *lspReader) readMessage() ([]byte, error) {\n\tcontentLength := -1\n\tfor {\n\t\tline, err := lr.reader.ReadString('\\n')","sourceCodeStart":55,"sourceCodeEnd":91,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/agent/copilot/jsonrpc.go#L55-L91","documentation":"After the header was written successfully, writing the JSON-RPC message body bytes to the copilot process's stdin pipe failed. Like the header write failure, this indicates the child process's stdin is closed or broken — typically the copilot process died between the header and body write or mid-request.","triggerScenarios":"call/notify/writeResponse/writeError issuing lw.w.Write(data) on a dead copilot process's stdin pipe; large messages hitting a full pipe buffer with a dead reader.","commonSituations":"Copilot CLI crash under memory pressure; context timeout killing the process during a large request write; race between process teardown and a pending RPC.","solutions":["Capture copilot stderr (replace io.Discard) to find why the process died","Retry the operation on a freshly created probe session","Check message size; ensure the reader goroutine is draining stdout so the pipe does not block","Verify ctx timeouts are generous enough for the RPC being sent"],"exampleFix":"// before\nif _, err := lw.w.Write(data); err != nil {\n    return fmt.Errorf(\"write body: %w\", err)\n}\n// after\nif _, err := lw.w.Write(data); err != nil {\n    return fmt.Errorf(\"write body (len=%d): %w\", len(data), err)\n} // extra context for diagnosing dead-process writes","handlingStrategy":"retry","validationCode":"// preflight liveness + reasonable payload size\nif closed := func() bool { select { case <-done: return true; default: return false } }(); closed {\n    return errors.New(\"probe process exited\")\n}\nif len(payload) > 1<<20 { return errors.New(\"rpc payload unusually large\") }","typeGuard":null,"tryCatchPattern":"if err := rpc.call(...); err != nil && strings.Contains(err.Error(), \"write body\") {\n    slog.Warn(\"rpc write failed mid-message; process likely died\", \"err\", err)\n    // recreate session and retry\n}","preventionTips":["Ensure the stdout reader goroutine always drains the pipe to avoid writer blocking","Capture child stderr to diagnose process death","Bound request payload sizes"],"tags":["io","jsonrpc","broken-pipe","process"],"backgroundTag":"broken-pipe","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"}