chenhg5/cc-connect · error
read header: %w
Error message
read header: %w
What it means
The lspReader failed while reading the header block of a Content-Length framed JSON-RPC message from the copilot process's stdout. ReadString('\n') returned an error — almost always io.EOF because the copilot process exited/closed stdout, or io.ErrUnexpectedEOF on truncated output. This is how a dead copilot process manifests on the read side.
Source
Thrown at agent/copilot/jsonrpc.go:93
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) {
contentLength := -1
for {
line, err := lr.reader.ReadString('\n')
if err != nil {
return nil, fmt.Errorf("read header: %w", err)
}
line = strings.TrimRight(line, "\r\n")
if line == "" {
// End of headers
break
}
if strings.HasPrefix(line, "Content-Length: ") {
val := strings.TrimPrefix(line, "Content-Length: ")
n, err := strconv.Atoi(strings.TrimSpace(val))
if err != nil {
return nil, fmt.Errorf("invalid Content-Length: %w", err)
}
contentLength = n
}
// Ignore other headers (Content-Type, etc.)
}
if contentLength < 0 {View on GitHub (pinned to 4000b2338a)
Solutions
- Check why the copilot process exited: capture stderr instead of io.Discard, inspect exit status
- Verify the copilot CLI is authenticated and runs without error when invoked manually
- Ensure copilot writes LSP frames to stdout and logs to stderr (a CLI printing logs to stdout breaks framing)
- Restart the probe session and retry the operation
Example fix
// before
cmd.Stderr = io.Discard // can't tell why stdout hit EOF
// after
var stderr bytes.Buffer
cmd.Stderr = &stderr
go func() { <-done; slog.Debug("copilot probe exited", "stderr", stderr.String()) }() Defensive patterns
Strategy: try-catch
Validate before calling
// smoke-test the CLI's framed output before relying on it
cmd := exec.Command(bin, args...)
var out, errb bytes.Buffer
cmd.Stdout, cmd.Stderr = &out, &errb
if err := cmd.Run(); err != nil || !bytes.HasPrefix(out, []byte("Content-Length: ")) {
return fmt.Errorf("copilot stdout not LSP-framed: %q stderr=%q", out.Bytes()[:min(64,len(out))], errb.String())
} Try / catch
if _, err := reader.readMessage(); err != nil {
if errors.Is(err, io.EOF) || strings.Contains(err.Error(), "read header") {
slog.Warn("copilot process closed stdout (likely exited)", "waitErr", cmd.Wait())
// restart probe session
}
} Prevention
- Never let the CLI or wrapper scripts print logs to stdout; stderr only
- Capture stderr instead of io.Discard in debug builds
- Authenticate the CLI beforehand (copilot auth) so it does not exit immediately
When it happens
Trigger: readMessage invoked by the reader goroutine or tests when the copilot process terminates or its stdout closes before a full header block arrives; malformed output with no trailing newline before EOF.
Common situations: Copilot CLI crashing or being killed (OOM, timeout); copilot writing non-framed diagnostics to stdout and then exiting; binary exiting immediately due to bad args or missing auth.
Related errors
AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06).
Data as JSON: /api/errors/dd864ac0e808ecf0.
Report an issue: GitHub.