{"record":{"id":"ad487bb16548672f","repo":"Kuberwastaken/claurst","slug":"lsp-message-missing-content-length-header","errorCode":null,"errorMessage":"LSP message missing Content-Length header","messagePattern":"LSP message missing Content-Length header","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src-rust/crates/core/src/lsp.rs","lineNumber":156,"sourceCode":"    reader: &mut BufReader<ChildStdout>,\r\n) -> anyhow::Result<serde_json::Value> {\r\n    let mut content_length: usize = 0;\r\n    loop {\r\n        let mut line = String::new();\r\n        let n = reader.read_line(&mut line).await?;\r\n        if n == 0 {\r\n            return Err(anyhow::anyhow!(\"LSP server closed stdout\"));\r\n        }\r\n        let trimmed = line.trim_end_matches(['\\r', '\\n']);\r\n        if trimmed.is_empty() {\r\n            break;\r\n        }\r\n        if let Some(val) = trimmed.strip_prefix(\"Content-Length: \") {\r\n            content_length = val.trim().parse()?;\r\n        }\r\n    }\r\n    if content_length == 0 {\r\n        return Err(anyhow::anyhow!(\"LSP message missing Content-Length header\"));\r\n    }\r\n    let mut buf = vec![0u8; content_length];\r\n    reader.read_exact(&mut buf).await?;\r\n    Ok(serde_json::from_slice(&buf)?)\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// LspClient\r\n// ---------------------------------------------------------------------------\r\n\r\ntype PendingMap = Arc<DashMap<u64, oneshot::Sender<serde_json::Value>>>;\r\n\r\n/// A running LSP client connected to a single server process.\r\npub struct LspClient {\r\n    pub server_name: String,\r\n    pub server_config: LspServerConfig,\r\n    /// The child process handle; `None` after shutdown.\r\n    process: Option<Child>,\r","sourceCodeStart":138,"sourceCodeEnd":174,"githubUrl":"https://github.com/Kuberwastaken/claurst/blob/b0637c97ec34144387cbf2f74f65df6d16a6cef1/src-rust/crates/core/src/lsp.rs#L138-L174","documentation":"LSP over stdio requires every message to start with a `Content-Length: <n>` header terminated by a blank line. `read_message` (called from `start`) parses headers until the blank line and, if no valid Content-Length was seen (`content_length == 0`), the stream is not a conforming LSP framing stream, so the client errors out rather than attempting an unbounded body read.","triggerScenarios":"The server's stdout emitted a header block with no `Content-Length:` line, or an empty/unparseable header value that defaulted content_length to 0 — e.g. the process printed plain-text diagnostics or an error banner on stdout instead of LSP-framed JSON-RPC.","commonSituations":"Configuring a plain CLI tool (or a server that logs to stdout) as the LSP command; a server that failed startup and printed a human-readable error; a wrapper script contaminating stdout with debug output; a non-LSP binary path in the config.","solutions":["Verify the configured command actually speaks LSP over stdio (e.g. run it and check for Content-Length framed output).","Redirect any non-protocol output to stderr: fix wrapper scripts to use `exec` and ensure the server doesn't print logs/diagnostics on stdout (use `--log-file` or stderr flags).","Check the server's stderr for a startup error explaining why it never entered LSP mode (bad root URI, missing initialization).","Confirm the header the server sends matches `Content-Length: <n>` exactly (some misbehaving servers use different casing/spacing — a protocol bug in the server)."],"exampleFix":"// before: wrapper pollutes stdout with logs\ng(language-server --verbose)  # verbose output goes to stdout\n\n// after\nexec language-server --log-file /tmp/ls.log  # stdout carries only LSP frames","handlingStrategy":"validation","validationCode":"// smoke-test that the command speaks LSP over stdio before configuring it:\nuse std::process::{Command, Stdio};\nlet mut child = Command::new(&server_cmd)\n    .stdin(Stdio::piped()).stdout(Stdio::piped()).stderr(Stdio::inherit())\n    .spawn()?;\n// send a minimal initialize request and expect a Content-Length-framed reply\nlet req = format!(\"Content-Length: 2\\r\\n\\r\\n{{}}\");\nuse std::io::Write;\nchild.stdin.take().unwrap().write_all(req.as_bytes())?;\nlet mut first = String::new();\nuse std::io::BufRead;\nstd::io::BufReader::new(child.stdout.take().unwrap()).read_line(&mut first)?;\nif !first.starts_with(\"Content-Length:\") {\n    anyhow::bail!(\"`{}` does not emit LSP framing on stdout\", server_cmd.display());\n}","typeGuard":"fn is_lsp_framed_output(line: &str) -> bool {\n    line.starts_with(\"Content-Length:\")\n}","tryCatchPattern":"match lsp.start().await {\n    Err(e) if e.to_string().contains(\"missing Content-Length\") => {\n        eprintln!(\"`{cmd}` did not speak LSP over stdio. Check it isn't printing logs/errors on stdout: {e:#}\");\n    }\n    other => other?,\n}","preventionTips":["Only register binaries that implement LSP over stdio as language servers.","Configure servers to log to a file or stderr — never let debug output reach stdout.","In wrapper scripts use `exec` so no extra output precedes the LSP stream.","Validate a new server with a manual initialize handshake before integrating it."],"tags":["lsp","stdio","protocol","ipc"],"backgroundTag":"invalid-json-response","analyzedSha":"b0637c97ec34144387cbf2f74f65df6d16a6cef1","analyzedAt":"2026-09-10T00:24:58.650Z","contentChangedAt":"2026-09-10T00:24:58.650Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}