NousResearch/hermes-agent · error · LSPProtocolError

malformed LSP header line: {line!r}

Error message

malformed LSP header line: {line!r}

What it means

LSPProtocolError raised when a decoded header line has no ':' separator — after stripping CRLF, partition(':') found an empty key. The base protocol requires 'Header-Name: value' lines, so a separator-free line means the server is emitting non-header content in the header region.

Source

Thrown at agent/lsp/protocol.py:104

                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)
    except ValueError as e:
        raise LSPProtocolError(f"non-integer Content-Length: {cl!r}") from e
    if n < 0 or n > 64 * 1024 * 1024:  # 64 MiB sanity cap
        raise LSPProtocolError(f"unreasonable Content-Length: {n}")

    try:
        body = await reader.readexactly(n)
    except asyncio.IncompleteReadError as e:
        raise LSPProtocolError(
            f"truncated LSP body: expected {n} bytes, got {len(e.partial)}"
        ) from e

View on GitHub (pinned to c896c09c42)

Solutions

  1. Eliminate all stdout output from the server and its launcher; route logs to stderr or a file.
  2. Confirm the server emits strict CRLF line endings in the base protocol (try a different/older version if it is a server bug).
  3. Reproduce the server's raw stdout (e.g. capture with a tee shim) to see exactly which line lacks the colon.

Example fix

# capture the raw wire to diagnose which line is malformed
# shim: stdio-tee.sh
# #!/bin/sh
# "$@" 2>>/tmp/lsp-server.log | tee /tmp/lsp-stdout.raw
LSPClient(cmd=["/path/stdio-tee.sh", "pyright-langserver", "--stdio"], ...)
Defensive patterns

Strategy: fallback

Try / catch

try:
    msg = await read_message(reader)
except LSPProtocolError as e:
    if "malformed LSP header" in str(e):
        capture_raw_stream_and_restart()  # tee stdout to a file, then fresh session
    else:
        raise

Prevention

When it happens

Trigger: A blank-but-not-empty line (e.g. only spaces — the parser only breaks on a truly empty line), a stray status line like 'Server ready' printed to stdout, or framing desync landing the parser mid-body.

Common situations: Servers or launch wrappers printing human-readable status/banners to stdout; a server writing LF-only line endings so CRLF stripping leaves residue; desync after a bad Content-Length.

Understand the failure class

Related errors


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