block/buzz · error · PARSE_ERROR

-32700

-32700

Error message

jsonrpc: parse: {e}

What it means

The buzz-agent harness speaks newline-delimited JSON-RPC over stdin. read_loop parses each non-empty line with serde_json; a line that is not valid JSON gets a JSON-RPC error response with code -32700 (parse error) and id null, and the loop continues with the next line — one bad line does not kill the agent.

Source

Thrown at crates/buzz-agent/src/lib.rs:275

    }
}

async fn read_loop<R: tokio::io::AsyncBufRead + Unpin>(
    mut stdin: R,
    app: Arc<App>,
    wire_tx: WireSender,
    max_line: usize,
) -> std::io::Result<()> {
    while let Some(line) = wire::read_bounded_line(&mut stdin, max_line).await? {
        if line.trim().is_empty() {
            continue;
        }
        match serde_json::from_str::<Value>(&line) {
            Ok(msg) => dispatch(&app, msg, &wire_tx).await,
            Err(e) => {
                wire::send(
                    &wire_tx,
                    wire::err(Value::Null, PARSE_ERROR, &format!("jsonrpc: parse: {e}")),
                )
                .await;
            }
        }
    }
    Ok(())
}

async fn dispatch(app: &Arc<App>, msg: Value, wire_tx: &WireSender) {
    match classify(&msg) {
        Inbound::Request { id, method, params } => {
            handle_request(app, id, method, params, wire_tx).await
        }
        Inbound::Notification { method, params } => handle_notification(app, &method, params).await,
        // Client's answer to a `session/request_permission` we issued. The
        // broker matches it to a live correlation id (waking that waiter) or
        // ignores an unknown/late id.
        Inbound::Response { id, result } => app.permissions.deliver(&id, result),

View on GitHub (pinned to dad5a33865)

Solutions

  1. Write exactly one compact, single-line JSON-RPC message per line and flush after each write
  2. Validate every outgoing message with a JSON serializer (never concatenate hand-built strings) before writing to stdin
  3. If the client is LSP-framed, strip Content-Length framing and emit raw NDJSON
  4. Check the spawning script for stray prints/echoes that pollute the stdin pipe

Example fix

// before (client, Node.js)
process.stdin.write(JSON.stringify(msg, null, 2)); // multi-line → -32700

// after
const line = JSON.stringify(msg); // compact, single line
if (line.includes("\n")) throw new Error("jsonrpc message spans multiple lines");
agent.stdin.write(line + "\n");
Defensive patterns

Strategy: validation

Validate before calling

function writeJsonRpc(stdin: NodeJS.WritableStream, msg: unknown): void {
  if (typeof msg !== "object" || msg === null) throw new TypeError("jsonrpc message must be an object");
  const line = JSON.stringify(msg); // compact — never pretty print
  if (line.includes("\n")) throw new Error("jsonrpc message must serialize to a single line");
  stdin.write(line + "\n");
}

Try / catch

// client side: a -32700 response means YOUR writer is broken, not the agent
if (resp.error?.code === -32700) {
  throw new Error(`framing bug: our last stdin line was not valid JSON: ${lastLineSent}`);
}

Prevention

When it happens

Trigger: Sending LSP-style framed messages (a 'Content-Length: 123' header line) instead of one JSON object per line; pretty-printed JSON split across multiple lines; a UTF-8 BOM prefix; stray non-JSON text (log lines, shell echo from a wrapper script) written into the agent's stdin.

Common situations: Clients ported from LSP assuming header framing; spawning scripts whose own stdout leaks into the child's stdin pipe; hand-rolled writers that forget to strip newlines inside string values; multiline JSON pasted into a debug console.

Related errors


AI-assisted analysis of block/buzz@dad5a33865 (2026-08-20). Data as JSON: /api/errors/57fd1254926fe8c1. Report an issue: GitHub.