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

  1. Verify the server command includes the stdio flag (usually --stdio) so nothing human-readable goes to stdout.
  2. 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).
  3. 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

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


AI-assisted analysis of NousResearch/hermes-agent@c896c09c42 (2026-08-14). Data as JSON: /api/errors/718c5a0d04c6e9a3. Report an issue: GitHub.