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

Ouverte
#10,807 10 commentaires 0 réactions 1 personne assignée Assignée à @lifeizhou-ap Voir sur GitHub
Langage dominant
Rust
Étoiles
54.2k
Forks
6.2k
Merge moyen
3 j 4 h
PR mergées (30 j)
240

Description

## 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.

Guide de contribution

Ouvrir le guide de contribution

Piste de recherche

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.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Évaluation

Stack technique
rust
Domaine
ai-infra-agents, backend
Type d'issue
Bug
Difficulté
3/5
Temps estimé
1-2 jours
Activité
Active
Clarté
Clairement spécifiée
Accessibilité débutants
65/100

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.