NousResearch/hermes-agent · error · LSPProtocolError
LSP header block exceeded 8 KiB without terminator
Error message
LSP header block exceeded 8 KiB without terminator
What it means
LSPProtocolError from the header parser's defensive cap: the server streamed more than 8 KiB of header bytes without ever sending the CRLF-CRLF terminator that ends a header block. Well-behaved LSP headers are ~50-100 bytes, so this indicates a non-conforming or broken server output stream.
Source
Thrown at agent/lsp/protocol.py:93
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:
raise LSPProtocolError(f"malformed LSP header line: {line!r}")
headers[key.strip().lower()] = value.strip()
cl = headers.get("content-length")
if cl is None:
raise LSPProtocolError(f"LSP message missing Content-Length: {headers!r}")
try:
n = int(cl)View on GitHub (pinned to c896c09c42)
Solutions
- Verify the server command includes the stdio flag (usually --stdio) so nothing human-readable goes to stdout.
- Wrap the server with a shim that redirects any non-LSP stdout to stderr, or use a quieter launcher (install the binary globally instead of npx so npx prints nothing).
- Check the server is a real LSP server at all — pointing the client at a plain CLI tool produces exactly this.
Example fix
# before LSPClient(cmd=["npx", "typescript-language-server", "--stdio"], ...) # npx may stream progress to stdout -> framing error # after LSPClient(cmd=["typescript-language-server", "--stdio"], ...) # npm i -g first
Defensive patterns
Strategy: validation
Validate before calling
# smoke-test the server speaks LSP before wiring it into a session
import subprocess
def server_speaks_lsp(cmd: list[str], init_request: bytes) -> bool:
p = subprocess.run(cmd, input=init_request, capture_output=True, timeout=10)
return p.stdout.startswith(b"Content-Length:") Prevention
- Always pass the server's stdio flag (usually --stdio).
- Install server binaries globally instead of via npx so launchers print nothing to stdout.
- Smoke-test a new server config with a handshake round-trip before enabling it.
When it happens
Trigger: A server (or anything sharing its stdout) writing an unbounded run of bytes without CRLF — e.g. a server printing a banner, stack trace, or progress output to stdout; a misconfigured wrapper script that forgets --stdio mode and emits interactive output.
Common situations: Forgetting --stdio on servers that default to IDE/interactive mode (jedi-language-server, bash-language-server variants); a wrapper (npx, docker run) printing download/progress messages to stdout; malformed Content-Length headers causing the parser to desync.
Related errors
- LSP server binary not found: {cmd[0]} ({e})
- unexpected EOF while reading LSP headers (partial={e.partial
- non-ASCII LSP header: {line!r}
- malformed LSP header line: {line!r}
- LSP message missing Content-Length: {headers!r}
AI-assisted analysis of NousResearch/hermes-agent@c896c09c42 (2026-08-14).
Data as JSON: /api/errors/718c5a0d04c6e9a3.
Report an issue: GitHub.