Hmbown/CodeWhale · error
duplicate LSP Content-Length
Error message
duplicate LSP Content-Length
What it means
LSP headers must contain at most one Content-Length field. When parse_header iterates the header lines and finds a second Content-Length, it cannot unambiguously determine the frame size and rejects the message. This protects against ambiguous framing from a non-conformant server.
Solutions
- Fix or remove the intermediary that duplicates the header.
- Check the server implementation: emit exactly one Content-Length per message.
- Capture the raw stream (tee stdout) and confirm the duplicate, then fix the producer.
- If writing your own LSP peer, reuse a battle-tested framing library instead of hand-rolling headers.
Example fix
// before (hand-rolled sender)
let header = "Content-Length: 42\r\nContent-Length: 42\r\n\r\n";
// after
let header = format!("Content-Length: {}\r\n\r\n", body.len()); Defensive patterns
Strategy: validation
Validate before calling
// when acting as an LSP peer, assert exactly one Content-Length per frame
let count = header.lines().filter(|l| l.to_ascii_lowercase().starts_with("content-length:")).count();
assert_eq!(count, 1, "duplicate Content-Length in outgoing header"); Try / catch
match parse_result {
Err(e) if e.to_string().contains("duplicate LSP Content-Length") => {
report_broken_peer();
disconnect_and_restart().await
}
other => other,
} Prevention
- Never hand-roll LSP headers; use a framing helper/library
- Test custom middleware for header rewriting
- Watch for case-variant duplicates (Content-Length vs content-length)
- Add a protocol conformance test to any in-house LSP server
When it happens
Trigger: Server sends a message whose header block contains two Content-Length lines (e.g. duplicated by a buggy proxy, a hand-rolled client/server implementation, or concatenated malformed frames).
Common situations: Custom LSP middleware that re-writes headers without removing the original, hand-written test doubles emitting both `Content-Length` and `content-length` (eq_ignore_ascii_case makes them collide), or frame concatenation bugs where two headers end up in one block.
Related errors
- LSP header exceeds size limit
- 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/472a9445885a7903.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/lsp/client.rs:602
/// 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")?;
if length == 0 || length > MAX_LSP_FRAME_BYTES {
return Err(anyhow!("LSP frame exceeds size limit or is empty"));
}
content_length = Some(length);
}
}
Ok(Some((
pos + 4,
content_length.context("missing LSP Content-Length")?,
)))
}
/// Background task that consumes inbound JSON values, classifies them asView on GitHub (pinned to 73e0f67d83)