chenhg5/cc-connect · error
write header: %w
Error message
write header: %w
What it means
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.
Source
Thrown at agent/copilot/jsonrpc.go:70
}
func newLSPWriter(w io.Writer) *lspWriter {
return &lspWriter{w: w}
}
func (lw *lspWriter) writeMessage(v any) error {
data, err := json.Marshal(v)
if err != nil {
return fmt.Errorf("marshal: %w", err)
}
header := fmt.Sprintf("Content-Length: %d\r\n\r\n", len(data))
lw.mu.Lock()
defer lw.mu.Unlock()
if _, err := io.WriteString(lw.w, header); err != nil {
return fmt.Errorf("write header: %w", err)
}
if _, err := lw.w.Write(data); err != nil {
return fmt.Errorf("write body: %w", err)
}
return nil
}
// lspReader reads Content-Length framed JSON-RPC messages.
type lspReader struct {
reader *bufio.Reader
}
func newLSPReader(r io.Reader) *lspReader {
return &lspReader{reader: bufio.NewReaderSize(r, 64*1024)}
}
// readMessage reads one Content-Length framed message and returns the raw JSON body.
func (lr *lspReader) readMessage() ([]byte, error) {View on GitHub (pinned to 4000b2338a)
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
Example fix
// before
resp, err := rpc.call(ctx, "session/delete", params) // write header: broken pipe
// after
resp, err := rpc.call(ctx, "session/delete", params)
if err != nil && isBrokenPipe(err) {
probe, err = a.newProbeSession(ctx) // restart and retry once
if err == nil { resp, err = probe.rpc.call(ctx, "session/delete", params) }
} Defensive patterns
Strategy: retry
Validate before calling
// check the probe process is still alive before sending
func (p *probeSession) alive() bool {
select { case <-p.done: return false; default: return true }
} Try / catch
resp, err := rpc.call(ctx, method, params)
if err != nil && (errors.Is(err, syscall.EPIPE) || strings.Contains(err.Error(), "write header")) {
// restart probe and retry once
if p2, e := a.newProbeSession(ctx); e == nil {
resp, err = p2.rpc.call(ctx, method, params)
}
} Prevention
- 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
When it happens
Trigger: 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).
Common situations: 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.
Related errors
AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06).
Data as JSON: /api/errors/01259201ff0feb2f.
Report an issue: GitHub.