NousResearch/hermes-agent · error · LSPProtocolError
unexpected EOF while reading LSP headers (partial={e.partial
Error message
unexpected EOF while reading LSP headers (partial={e.partial!r}) What it means
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.
Source
Thrown at agent/lsp/protocol.py:85
"""Read one Content-Length framed JSON-RPC message from the stream.
Returns ``None`` on clean EOF (server closed stdout cleanly between
messages — typical shutdown). Raises :class:`LSPProtocolError` on
malformed framing.
The reader is advanced to just past the JSON body on success.
"""
headers: dict = {}
header_bytes = 0
while True:
try:
line = await reader.readuntil(b"\r\n")
except asyncio.IncompleteReadError as e:
# EOF while reading headers. If we hadn't started a header
# block, treat as clean EOF; otherwise the framing is bad.
if not e.partial and not headers:
return None
raise LSPProtocolError(
f"unexpected EOF while reading LSP headers (partial={e.partial!r})"
) from e
# Defensive cap against a server streaming headers without ever
# emitting CRLF-CRLF. Caps total header bytes at 8 KiB — a
# well-behaved server fits in well under 200 bytes.
header_bytes += len(line)
if header_bytes > 8192:
raise LSPProtocolError(
"LSP header block exceeded 8 KiB without terminator"
)
line = line[:-2] # strip CRLF
if not line:
break # blank line ends header block
try:
key, _, value = line.decode("ascii").partition(":")
except UnicodeDecodeError as e:
raise LSPProtocolError(f"non-ASCII LSP header: {line!r}") from e
if not key:View on GitHub (pinned to c896c09c42)
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.
Defensive patterns
Strategy: fallback
Try / catch
try:
msg = await read_message(reader)
except LSPProtocolError as e:
if "unexpected EOF" in str(e):
mark_client_dead_and_fallback_to_no_lsp() # degrade to non-LSP analysis
else:
raise Prevention
- 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.
When it happens
Trigger: 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.
Common situations: OOM-killed language servers; containers hitting memory limits; servers that log to stdout (breaking the protocol) and then exit.
Related errors
- truncated LSP body: expected {n} bytes, got {len(e.partial)}
- LSP server binary not found: {cmd[0]} ({e})
- cannot send {method!r}: stdin closed
- send failed for {method!r}: {e}
- LSP header block exceeded 8 KiB without terminator
AI-assisted analysis of NousResearch/hermes-agent@c896c09c42 (2026-08-14).
Data as JSON: /api/errors/39281eb04663d30e.
Report an issue: GitHub.