aaif-goose / aaif-goose/goose

ACP harness tool calls poison session history: tool_use.name >200 chars causes permanent 400 after switching to Anthropic-format providers

オープン
#10,807 コメント 10 件 リアクション 0 件 担当者 1 名 @lifeizhou-ap に割り当て済み GitHub で見る
主要言語
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

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。