{"record":{"id":"01259201ff0feb2f","repo":"chenhg5/cc-connect","slug":"write-header-w","errorCode":null,"errorMessage":"write header: %w","messagePattern":"write header: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"agent/copilot/jsonrpc.go","lineNumber":70,"sourceCode":"}\n\nfunc newLSPWriter(w io.Writer) *lspWriter {\n\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) {","sourceCodeStart":52,"sourceCodeEnd":88,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/agent/copilot/jsonrpc.go#L52-L88","documentation":"Writing the LSP 'Content-Length: N\\r\\n\\r\\n' header to the copilot process's stdin pipe failed. This happens when the child process has exited and its stdin pipe is closed, or the underlying writer errored (EPIPE). It is a transport-level failure of the JSON-RPC framing writer shared by call/notify/respond.","triggerScenarios":"Any JSON-RPC call/notify/writeResponse/writeError to a copilot probe process that has already died, or whose stdin pipe broke (broken pipe on process exit).","commonSituations":"Copilot CLI crashing mid-session (check `dmesg`/crash logs); probe timeout context cancelled, killing the process while a request is in flight; sending a request after DeleteSession closed the session.","solutions":["Check whether the copilot process exited prematurely (remove io.Discard on stderr while debugging to capture its output)","Recreate the probe session and retry the request","Ensure requests are not sent after session close/timeout; check ctx deadlines","Handle EPIPE by restarting the copilot process transparently"],"exampleFix":"// before\nresp, err := rpc.call(ctx, \"session/delete\", params) // write header: broken pipe\n// after\nresp, err := rpc.call(ctx, \"session/delete\", params)\nif err != nil && isBrokenPipe(err) {\n    probe, err = a.newProbeSession(ctx) // restart and retry once\n    if err == nil { resp, err = probe.rpc.call(ctx, \"session/delete\", params) }\n}","handlingStrategy":"retry","validationCode":"// check the probe process is still alive before sending\nfunc (p *probeSession) alive() bool {\n    select { case <-p.done: return false; default: return true }\n}","typeGuard":null,"tryCatchPattern":"resp, err := rpc.call(ctx, method, params)\nif err != nil && (errors.Is(err, syscall.EPIPE) || strings.Contains(err.Error(), \"write header\")) {\n    // restart probe and retry once\n    if p2, e := a.newProbeSession(ctx); e == nil {\n        resp, err = p2.rpc.call(ctx, method, params)\n    }\n}","preventionTips":["Check the process-done channel before issuing RPCs","Keep copilot stderr captured in debug builds to catch early exits","Use context timeouts slightly longer than expected RPC latency"],"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-14T00:17:10.932Z"}