block / block/buzz

buzz-acp advertises `protocolVersion: 2` but implements v1 semantics — every v2-capable ACP adapter fails (initialize -32602, then missing stopReason)

Open
#4,728 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
Rust
Stars
32.7k
Forks
4.3k
Avg merge
1d 13h
Merged PRs (30d)
253

Description

## Summary

`buzz-acp` hardcodes `"protocolVersion": 2` in its `initialize` request but implements ACP **v1** semantics throughout. Any adapter that actually supports v2 therefore negotiates v2, behaves correctly for v2, and breaks Buzz — twice, at two different layers.

This blocks the path recommended in #2393, where the generic answer to "please support runtime X" is that any ACP-over-stdio binary can be registered from Settings. That is true only for v1-only adapters. A v2-capable adapter cannot be registered today.

Found while wiring Google Antigravity CLI (`agy`) via the third-party adapter [`agy-acp`](https://www.npmjs.com/package/agy-acp) as a Custom harness, which is precisely the flow #2393 was closed in favour of.

## Symptom 1: initialize fails with -32602

```
INFO buzz_acp: buzz-acp starting: agent_cmd=/Users/ash/.local/bin/agy-acp ... agents=10
ERROR buzz_acp: agent initialize failed: Agent reported error (code -32602): Invalid params agent=0
... identical for agents 1-9 ...
Error: all 10 agents failed to start — cannot continue
```

`build_initialize_params()` (`crates/buzz-acp/src/acp.rs:126`) sends `protocolVersion: 2` with a v1-shaped body: `clientCapabilities` and `clientInfo`, and no `info`. Draft ACP v2 renames `clientInfo` to `info` and makes it **required**, so a v2 router validates against the v2 schema and rejects the handshake.

### Repro

```bash
npm i -g agy-acp@0.4.3
printf '%s\n' '{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":2,"clientCapabilities":{"auth":{"terminal":true},"_meta":{"goose":{"customNotifications":true},"terminal-auth":true}},"clientInfo":{"name":"buzz-acp","version":"0.1.0"}}}' | agy-acp
```

Actual:

```json
{"jsonrpc":"2.0","id":0,"error":{"code":-32602,"message":"Invalid params","data":"invalid initialize params: [{\"expected\":\"object\",\"code\":\"invalid_type\",\"path\":[\"info\"],\"message\":\"Invalid input: expected object, received undefined\"}]"}}
```

Isolated across 15 payload variants: `clientCapabilities`, `_meta`, `auth` and the presence of `clientInfo` are all irrelevant, and the schema tolerates unknown keys. It is strict only about `info`. Rejecting schema is `@agentclientprotocol/sdk@1.3.0`, `dist/v2/schema/zod.gen.js:2488` (`info: zImplementation`, no `.optional()`), thrown from `dist/protocol-router.js:399`.

## Symptom 2: adding `info` moves the failure one layer down

Injecting `params.info` makes the handshake succeed with `result.protocolVersion == 2`. The pool starts, the agent joins the channel and reports commands available. The first prompt then fails:

```
Turn error · error: Protocol error: session/prompt response missing stopReason
```

This is not a second bug. It is the same one surfacing where it actually matters. From `agy-acp`'s own source (`dist/acp/agent.js:261-268`):

> v1 prompt lifecycle: response carries `stopReason` after the full turn.
> v2: progress and `stopReason` arrive as `state_update` notifications.

Under v2 the prompt response has no `stopReason` by design. `buzz-acp` waits for it in the response, which is v1 behaviour. So Buzz asks for v2, gets v2, and cannot consume it.

## Why it works with the bundled adapters

`claude-agent-acp` is v1-only. It parses `{"protocolVersion": 2}` fine, clamps its reply to `"protocolVersion": 1`, and `buzz-acp` accepts the downgrade. The tolerant v1 schema is the only one that ever runs, so the mismatch stays invisible. Same for `codex-acp`. It surfaces the moment an adapter is capable of honouring the version Buzz asked for.

## Impact

Every pool slot fails identically before any prompt, so the run aborts. No v2-capable ACP adapter can be used with Buzz today, and the failure is opaque: `-32602 Invalid params` with no indication that a protocol version is involved.

## Suggested fix

Either:

1. Send `info: {name, version}` alongside `clientInfo` in the initialize params (v1 agents ignore unknown keys, so one body serves both) **and** implement the v2 prompt lifecycle, consuming `stopReason` from `state_update` notifications; or
2. Revert the pin at `acp.rs:126` to `"protocolVersion": 1` until the v2 body and lifecycle are implemented.

(2) is the smaller change and restores correctness immediately. The comment at that line notes Buzz is squatting on ACP v2 ahead of the upstream RFD; the problem is that adapters take the advertised version at face value.

## Secondary: the error is undiagnosable from the logs

`agent_error_from_json` (`crates/buzz-acp/src/acp.rs:115`) surfaces only `error.message`. The v2 SDK puts the zod detail in `error.data` and leaves `message` as the bare string `"Invalid params"`, so operators see a detail-free error ten times over. Logging `error.data` on initialize failure would have made this self-diagnosing in seconds. Related: #4069 notes the same `error.data` loss for a different error, and #3338 covers the misleading "all N agents failed to start" wording.

## Workaround

A stdio shim that clamps the initialize request to v1 before forwarding, leaving everything else verbatim. Verified working end to end with `agy-acp@0.4.3`:

```js
#!/usr/bin/env node
const { spawn } = require("node:child_process");
const target = process.env.AGY_ACP_BIN; // real adapter entry
const child = spawn(process.execPath, [target, ...process.argv.slice(2)],
{ stdio: ["pipe", "pipe", "inherit"], env: process.env });
child.stdout.pipe(process.stdout);
child.on("exit", (c, s) => process.exit(s ? 1 : (c ?? 0)));

let patched = false, buf = "";
const clamp = (line) => {
if (patched || !line.includes('"initialize"')) return line;
let m; try { m = JSON.parse(line); } catch { return line; }
if (m.method !== "initialize" || !m.params) return line;
patched = true;
if (typeof m.params.protocolVersion === "number" && m.params.protocolVersion > 1) {
m.params.protocolVersion = 1;
return JSON.stringify(m);
}
return line;
};
process.stdin.on("data", (c) => {
buf += c.toString("utf8");
let i;
while ((i = buf.indexOf("\n")) !== -1) {
const line = buf.slice(0, i); buf = buf.slice(i + 1);
child.stdin.write((line.trim() ? clamp(line) : line) + "\n");
}
});
process.stdin.on("end", () => { if (buf.length) child.stdin.write(buf); child.stdin.end(); });
```

Register that as the Custom harness Command instead of the adapter itself.

## Versions

- Buzz Desktop v0.5.4, buzz-acp 0.1.0
- `agy-acp` 0.4.3, `@agentclientprotocol/sdk` 1.3.0
- Google Antigravity CLI (`agy`), macOS 15 (Darwin 25.5.0), node 22.23.1
- Relay: managed `*.communities.buzz.xyz`

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.