chenhg5/cc-connect · error

missing Content-Length header

Error message

missing Content-Length header

What it means

readMessage parses the LSP-style JSON-RPC framing from the copilot child process stream. Every LSP message must begin with a `Content-Length:` header; the parser initializes contentLength to -1 and if no such header was seen before the blank line it returns "missing Content-Length header". This guards against garbage bytes, protocol desync, or a non-LSP stream coming from the child.

Source

Thrown at agent/copilot/jsonrpc.go:112

		}
		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 {
		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

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Ensure every emitted message begins with a `Content-Length: <N>\r\n` header followed by a blank line before the N-byte body
  2. Check what the child process actually wrote to stdout (log raw bytes) to confirm it is LSP-framed
  3. Verify the copilot CLI version is one that speaks vscode-jsonrpc framing
  4. If feeding the reader in tests, use a helper that builds the header from len(body) instead of hand-writing it

Example fix

// before: raw JSON written to the pipe
fmt.Fprintf(w, `{"jsonrpc":"2.0","result":{},"id":1}\n`)
// after: LSP-framed message
body := `{"jsonrpc":"2.0","result":{},"id":1}`
fmt.Fprintf(w, "Content-Length: %d\r\n\r\n%s", len(body), body)
Defensive patterns

Strategy: validation

Validate before calling

func isLSPFramed(stream []byte) bool {
    return bytes.HasPrefix(stream, []byte("Content-Length:"))
}

Type guard

func hasContentLength(header string) bool {
    return strings.Contains(strings.ToLower(header), "content-length:")
}

Try / catch

body, err := readMessage(lr)
if err != nil {
    if strings.Contains(err.Error(), "missing Content-Length") {
        slog.Warn("copilot: non-LSP output on stdout, draining", "err", err)
        return errRecoverable
    }
    return err
}

Prevention

When it happens

Trigger: Calling readMessage (directly via LSPReader or via runMockCopilot / the reader goroutine) on a stream that produced headers without a Content-Length line — e.g. raw JSON without framing, a partial header line split without its CRLF, or headers like `Content-Type: application/vscode-jsonrpc; charset=utf-8` only.

Common situations: Mock/stdin scripts written for tests that forget the Content-Length header; the copilot CLI version changed its output framing; stderr noise accidentally piped into stdout; a manually crafted test fixture with only `\r\n` line endings mismatched.

Related errors


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