{"record":{"id":"39281eb04663d30e","repo":"NousResearch/hermes-agent","slug":"unexpected-eof-while-reading-lsp-headers-partial","errorCode":null,"errorMessage":"unexpected EOF while reading LSP headers (partial={e.partial!r})","messagePattern":"unexpected EOF while reading LSP headers \\(partial=(.+?)\\)","errorType":"exception","errorClass":"LSPProtocolError","httpStatus":null,"severity":"error","filePath":"agent/lsp/protocol.py","lineNumber":85,"sourceCode":"    \"\"\"Read one Content-Length framed JSON-RPC message from the stream.\n\n    Returns ``None`` on clean EOF (server closed stdout cleanly between\n    messages — typical shutdown).  Raises :class:`LSPProtocolError` on\n    malformed framing.\n\n    The reader is advanced to just past the JSON body on success.\n    \"\"\"\n    headers: dict = {}\n    header_bytes = 0\n    while True:\n        try:\n            line = await reader.readuntil(b\"\\r\\n\")\n        except asyncio.IncompleteReadError as e:\n            # EOF while reading headers.  If we hadn't started a header\n            # block, treat as clean EOF; otherwise the framing is bad.\n            if not e.partial and not headers:\n                return None\n            raise LSPProtocolError(\n                f\"unexpected EOF while reading LSP headers (partial={e.partial!r})\"\n            ) from e\n        # Defensive cap against a server streaming headers without ever\n        # emitting CRLF-CRLF.  Caps total header bytes at 8 KiB — a\n        # well-behaved server fits in well under 200 bytes.\n        header_bytes += len(line)\n        if header_bytes > 8192:\n            raise LSPProtocolError(\n                \"LSP header block exceeded 8 KiB without terminator\"\n            )\n        line = line[:-2]  # strip CRLF\n        if not line:\n            break  # blank line ends header block\n        try:\n            key, _, value = line.decode(\"ascii\").partition(\":\")\n        except UnicodeDecodeError as e:\n            raise LSPProtocolError(f\"non-ASCII LSP header: {line!r}\") from e\n        if not key:","sourceCodeStart":67,"sourceCodeEnd":103,"githubUrl":"https://github.com/NousResearch/hermes-agent/blob/c896c09c42910c584c4c7d2325b58c14713ea42c/agent/lsp/protocol.py#L67-L103","documentation":"LSPProtocolError from the wire-framing reader: StreamReader.readuntil(b\"\\r\\n\") hit EOF partway through a header block. If nothing had been read and no headers accumulated, EOF is treated as a clean shutdown (returns None); a partial line or previously-parsed headers means the server died mid-message and the framing is corrupt.","triggerScenarios":"Server process crashing or being killed exactly while emitting a response header block; server closing stdout without a final CRLF-terminated blank line after some headers; truncated output from a server killed by OOM.","commonSituations":"OOM-killed language servers; containers hitting memory limits; servers that log to stdout (breaking the protocol) and then exit.","solutions":["Enable debug logging of the client's drained stderr to find the server's crash reason.","Raise memory limits / disable heavy features for memory-hungry servers (clangd with large projects is the classic case).","Ensure the server writes logs to stderr, never stdout — stdout is the LSP wire and any stray output corrupts framing."],"exampleFix":null,"handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"try:\n    msg = await read_message(reader)\nexcept LSPProtocolError as e:\n    if \"unexpected EOF\" in str(e):\n        mark_client_dead_and_fallback_to_no_lsp()  # degrade to non-LSP analysis\n    else:\n        raise","preventionTips":["Treat a clean pre-header EOF (returns None) as normal shutdown and a partial one as a crash — handle both explicitly.","Enable debug stderr logging so server crash causes are captured.","Give memory-hungry servers adequate limits; OOM kills are the top EOF-mid-message source."],"tags":["lsp","protocol","subprocess","eof"],"backgroundTag":null,"analyzedSha":"c896c09c42910c584c4c7d2325b58c14713ea42c","analyzedAt":"2026-08-14T17:18:01.089Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}