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

  1. Check why the copilot process exited: capture stderr instead of io.Discard, inspect exit status
  2. Verify the copilot CLI is authenticated and runs without error when invoked manually
  3. Ensure copilot writes LSP frames to stdout and logs to stderr (a CLI printing logs to stdout breaks framing)
  4. 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

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.