chenhg5/cc-connect · error

read body: %w

Error message

read body: %w

What it means

After validating the Content-Length header, readMessage allocates a buffer of exactly contentLength bytes and uses io.ReadFull to read the body. If the stream ends (io.EOF/io.ErrUnexpectedEOF) or any read error occurs before contentLength bytes arrive, the error is wrapped as "read body: %w".

Source

Thrown at agent/copilot/jsonrpc.go:120

			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 {
		return nil, fmt.Errorf("missing Content-Length header")
	}
	if contentLength > 10*1024*1024 {
		return nil, fmt.Errorf("Content-Length too large: %d", contentLength)
	}

	body := make([]byte, contentLength)
	if _, err := io.ReadFull(lr.reader, body); err != nil {
		return nil, fmt.Errorf("read body: %w", err)
	}
	return body, nil
}

// rpcClient manages JSON-RPC request IDs and pending responses.
type rpcClient struct {
	writer *lspWriter
	nextID atomic.Int64

	pendingMu sync.Mutex
	pending   map[int64]chan *jsonRPCResponse
}

func newRPCClient(w io.Writer) *rpcClient {
	c := &rpcClient{
		writer:  newLSPWriter(w),
		pending: make(map[int64]chan *jsonRPCResponse),
	}

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Inspect the child process state — check for a crash (see stderr buffer / exit status) that truncated the message
  2. Fix the writer so Content-Length exactly matches the body byte count
  3. Retry the session: truncation usually means the child is gone and the session must be restarted
  4. For mocks, build messages with a helper computing len(body) instead of hard-coded lengths

Example fix

// before: header/body mismatch truncates the reader
fmt.Fprintf(w, "Content-Length: 42\r\n\r\n%s", shortBody)
// after
fmt.Fprintf(w, "Content-Length: %d\r\n\r\n%s", len(body), body)
Defensive patterns

Strategy: try-catch

Try / catch

body, err := readMessage(lr)
if err != nil {
    var eofLike = errors.Is(err, io.ErrUnexpectedEOF) || strings.Contains(err.Error(), "read body")
    if eofLike {
        slog.Warn("copilot: message truncated, child likely died — restarting session")
        return errSessionDead
    }
    return err
}

Prevention

When it happens

Trigger: The copilot process dies or closes stdout after sending the header but before/within the body; the declared Content-Length is larger than the actual bytes written; the pipe is broken mid-read; a mock stream terminates early.

Common situations: Copilot CLI crash or OOM kill mid-message; network-like stream truncation with stdio when the child exits; a test mock that writes a header with length N but only M<N bytes; header says 100 but body contains trailing data missing bytes.

Related errors


AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06). Data as JSON: /api/errors/42859b17739e38f0. Report an issue: GitHub.