Skip to content

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

Description

@morgmart

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:

// 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

Both point at the same underlying gap: harness switching has no principled history-translation layer that shapes prior turns to the destination's constraints.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

Status
Accepted / design

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions