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

  1. Check whether the copilot process exited prematurely (remove io.Discard on stderr while debugging to capture its output)
  2. Recreate the probe session and retry the request
  3. Ensure requests are not sent after session close/timeout; check ctx deadlines
  4. 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

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.