ACP harness tool calls poison session history: tool_use.name >200 chars causes permanent 400 after switching to Anthropic-format providers
- 主要言語
- Rust
- スター
- 54.2k
- フォーク
- 6.2k
- 平均マージ
- 3日 4時間
- マージ済み PR(30日)
- 240
説明
## Summary
Any session that ran turns on an **ACP harness** (Codex, Claude Code — any ACP agent) becomes **permanently unusable on Anthropic-format providers** after switching the session to a native Goose model. ACP tool calls are stored in session history with the harness's display *title* — frequently the full shell command — as the tool **name**. Anthropic rejects `tool_use.name` over 200 characters, so every subsequent prompt replays the poisoned history and fails with the same 400. Retrying can never succeed; the invalid data is persisted in the session.
```
Error: Request failed: Bad request (400):
{"message":"messages.313.content.1.tool_use.name: String should have at most 200 characters"}
```
## Repro
1. Start a session on an ACP harness (reproduced with both `codex-acp` and `claude-acp`)
2. Have it run a shell command longer than 200 characters (a long `git commit`/`git worktree` one-liner is enough)
3. Switch the session's model to a native Goose provider that uses the Anthropic message format (reproduced via `databricks_v2` → Claude)
4. Send any prompt → 400 above; every retry fails identically
## Root cause
Two layers combine:
**Write time — `crates/goose/src/acp/provider.rs`:** when converting ACP `SessionUpdate::ToolCall` notifications into session messages, the display title is stored as the tool name:
```rust
// ACP carries no canonical tool name to clients — only
// `title` (display) and `kind` (category). ...
let _ = tx.try_send(AcpUpdate::ToolCallStart {
id: id.clone(),
name: tool_call.title.clone(), // <-- often the full shell command
kind: tool_call.kind,
raw_input: tool_call.raw_input.clone(),
});
```
For shell-type calls, harnesses commonly set `title` to the entire command string. The ACP harness's own API doesn't mind, so nothing fails until later.
**Read time — `crates/goose-provider-types/src/formats/anthropic.rs`:** when formatting history for Anthropic-style APIs, `tool_call.name` is emitted verbatim into the `tool_use` block (`NAME_FIELD: tool_call.name`, two sites) with no length sanitization. Anthropic enforces a 200-character maximum on `tool_use.name`.
Note this formatter already defends against a sibling class of replay-poisoning (placeholder `tool_use` for unparseable calls so `tool_result` isn't orphaned; non-object `input` coercion documented as fixing another 400-on-replay) — tool-name length is the same category of problem, currently unhandled.
## Impact
- Model switching away from an ACP harness silently breaks the session: the user sees a generic request failure and "try again" loops forever
- On one developer machine we found **13 affected sessions** across `codex-acp` and `claude-acp` histories, with stored tool names up to **2,143 characters**
- The failure appears long after the cause (whenever the user later switches models), making it very hard for users to connect cause and effect
## Expected behavior
Switching a session between harnesses/providers should never produce history the destination provider rejects.
## Suggested fix (both layers)
1. **Write time:** store a canonical short tool name for ACP tool calls (e.g. derive from `kind`, or a fixed name like `acp_tool`), keeping the display title in metadata/arguments where the renderer can still show it. `provider.rs` already passes `kind` through `tool_meta` (`goose.acp.kind`), so the display concern is separable from the wire name.
2. **Read time (defensive, and heals existing sessions):** sanitize/truncate `tool_use.name` to the provider's limit when formatting messages in `formats/anthropic.rs` (and audit other formats — Bedrock's converter builds `tool_use` blocks too). Without this, every already-poisoned session stays permanently broken even after the write-time fix.
## Related but distinct
- #10660 — hardens the Goose→ACP handoff memo lifecycle (opposite switch direction; doesn't touch native-provider history replay)
- #10751 — transport-level WebSocket drop on oversized prompts (connection layer, not message-content validity)
Both point at the same underlying gap: harness switching has no principled history-translation layer that shapes prior turns to the destination's constraints.
コントリビューションガイド
調査の方向性
The issue points to crates/goose/src/acp/provider.rs where tool names are stored and crates/goose-provider-types/src/formats/anthropic.rs where they are formatted. Start by examining how ACP tool calls are converted to session messages and where the title becomes the name. Then look at the Anthropic formatter to see where the name length limit is enforced. A fix likely involves truncating or replacing the stored name in the formatter and possibly adjusting the provider to store a shorter canonical name. Run existing tests to ensure changes don't break history replay.
索引モデルが issue の本文から書いたものです。
評価
- 技術スタック
- rust
- 領域
- ai-infra-agents, backend
- issue の種類
- バグ
- 難易度
- 3/5
- 見積もり時間
- 1〜2日
- 活発さ
- 活発
- 明瞭さ
- 明確に書かれている
- 初心者へのやさしさ
- 65/100