Hmbown/CodeWhale · error
LSP header exceeds size limit
Error message
LSP header exceeds size limit
What it means
parse_header scans the inbound byte buffer for the \r\n\r\n header terminator when no terminator has been found yet. If the accumulated buffer exceeds MAX_LSP_HEADER_BYTES without a terminator, the header can never be valid, so the client errors out. This exists so a broken or malicious LSP server cannot make the input buffer grow indefinitely.
Solutions
- Verify the configured LSP server binary actually implements the LSP stdio transport (Content-Length headers) rather than line-delimited JSON.
- Check that the server's stdout is clean — redirect debug logging to stderr, not stdout.
- Restart the server process; the stream is desynchronized and cannot recover mid-frame.
- If the server legitimately sends huge headers, raise MAX_LSP_HEADER_BYTES (with care) or upgrade the server.
Example fix
// before (wrapper script) my-lsp-server --verbose # logs to stdout // after my-lsp-server --verbose 2>server.log # keep stdout protocol-only
Defensive patterns
Strategy: validation
Validate before calling
// sanity-check the server speaks LSP framing before wiring it up
let probe = std::process::Command::new(&server_bin).arg("--version").output()?;
if !probe.status.success() { return Err("binary is not a working LSP server"); } Try / catch
match reader_result {
Err(e) if e.to_string().contains("header exceeds size limit") => {
// stream is desynchronized: kill and restart the server
server.kill().await.ok();
restart_with_framing_check().await
}
other => other,
} Prevention
- Ensure server logs go to stderr, never stdout
- Smoke-test servers with an LSP conformance probe before registering them
- Never wrap the server in scripts that echo to stdout
- Cap restart loops so a permanently broken server fails fast
When it happens
Trigger: reader_task receives bytes from the server's stdout that never contain \r\n\r\n within MAX_LSP_HEADER_BYTES; e.g. the server emits binary garbage, raw JSON without an LSP framing header, or a corrupted stream.
Common situations: Pointing the client at a server that speaks plain JSON-RPC over stdio without Content-Length framing, a crashed server dumping a stack trace or binary output to stdout, or stdout/stderr wiring mixed up in a wrapper script.
Related errors
- duplicate LSP Content-Length
- LSP frame exceeds size limit or is empty
- LSP initialize response is missing server capabilities
- Codewhale stream-json contained an unknown event type
- {err}
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/f42ef220f895f359.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/lsp/client.rs:589
}
let value = match serde_json::from_slice::<Value>(&buf[header_end..frame_end]) {
Ok(value) => value,
Err(_) => return,
};
buf.drain(..frame_end);
if tx.send(value).await.is_err() {
return;
}
}
}
}
/// Distinguish incomplete headers from malformed or oversized frames so a
/// broken server cannot cause an indefinitely growing input buffer.
fn parse_header(buf: &[u8]) -> Result<Option<(usize, usize)>> {
let Some(pos) = buf.windows(4).position(|window| window == b"\r\n\r\n") else {
if buf.len() > MAX_LSP_HEADER_BYTES {
return Err(anyhow!("LSP header exceeds size limit"));
}
return Ok(None);
};
if pos + 4 > MAX_LSP_HEADER_BYTES {
return Err(anyhow!("LSP header exceeds size limit"));
}
let header = std::str::from_utf8(&buf[..pos]).context("invalid LSP header encoding")?;
let mut content_length = None;
for line in header.split("\r\n") {
let (name, value) = line.split_once(':').context("malformed LSP header")?;
if name.eq_ignore_ascii_case("Content-Length") {
if content_length.is_some() {
return Err(anyhow!("duplicate LSP Content-Length"));
}
let length = value
.trim()
.parse::<usize>()
.context("invalid LSP Content-Length")?;View on GitHub (pinned to 73e0f67d83)