ultraworkers/claw-code · error · std::io::Error

missing Content-Length header

Error message

missing Content-Length header

What it means

Server-side `read_frame` (runtime/src/mcp_server.rs:281): the header block terminated with a blank line (`\r\n` or `\n` both accepted here) but no header named Content-Length (matched case-insensitively after trimming) was parsed. Header lines without a `:` separator are silently ignored, so a misspelled or absent Content-Length lands here. `ErrorKind::InvalidData`.

Source

Thrown at rust/crates/runtime/src/mcp_server.rs:281

        }
        first_header = false;
        if line == "\r\n" || line == "\n" {
            break;
        }
        let header = line.trim_end_matches(['\r', '\n']);
        if let Some((name, value)) = header.split_once(':') {
            if name.trim().eq_ignore_ascii_case("Content-Length") {
                let parsed = value
                    .trim()
                    .parse::<usize>()
                    .map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;
                content_length = Some(parsed);
            }
        }
    }

    let content_length = content_length.ok_or_else(|| {
        io::Error::new(io::ErrorKind::InvalidData, "missing Content-Length header")
    })?;
    let mut payload = vec![0_u8; content_length];
    reader.read_exact(&mut payload).await?;
    Ok(Some(payload))
}

async fn write_response(
    stdout: &mut Stdout,
    response: &JsonRpcResponse<JsonValue>,
) -> io::Result<()> {
    let body = serde_json::to_vec(response)
        .map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;
    let header = format!("Content-Length: {}\r\n\r\n", body.len());
    stdout.write_all(header.as_bytes()).await?;
    stdout.write_all(&body).await?;
    stdout.flush().await
}

View on GitHub (pinned to 08106b0c37)

Solutions

  1. Fix the client to emit LSP-style framing: `Content-Length: <byte-len>\r\n\r\n<body>` with the exact byte length of the UTF-8 body.
  2. Test the client against a reference MCP peer to confirm framing before pointing it at claw.
  3. If you control a proxy in between, make sure it forwards the Content-Length header unmodified.

Example fix

# before (client writes headers without length)
printf 'Content-Type: application/json\r\n\r\n{"jsonrpc":"2.0"}' | claw mcp-serve

# after
BODY='{"jsonrpc":"2.0","id":1,"method":"initialize"}'
printf 'Content-Length: %d\r\n\r\n%s' "${#BODY}" "$BODY" | claw mcp-serve
Defensive patterns

Strategy: try-catch

Try / catch

match read_frame(&mut reader).await {
    Err(e) if e.kind() == io::ErrorKind::InvalidData
        && e.to_string().contains("missing Content-Length") => {
        // protocol violation by the peer: close the connection, log the offending client
    }
    other => other,
}

Prevention

When it happens

Trigger: A client sending only `Content-Type: application/vscode-jsonrpc; charset=utf-8\r\n\r\n` with no length; `Content_length` misspelling (split at ':' works but the name match fails, so it is ignored); a hand-rolled client that sends the JSON body inline where headers should be and a stray blank line follows.

Common situations: Writing a custom MCP client and assuming newline-delimited JSON or that Content-Length is optional; adapting an HTTP client that sends Content-Type first and forgets the length; proxy middleware stripping the header.

Related errors


AI-assisted analysis of ultraworkers/claw-code@08106b0c37 (2026-08-18). Data as JSON: /api/errors/dea244f5ae5b6862. Report an issue: GitHub.