Skip to content

fix(session): sanitize malformed tool names in pending parts#29156

Open
divitkashyap wants to merge 1 commit into
anomalyco:devfrom
divitkashyap:fix/pending-tool-malformed-28989
Open

fix(session): sanitize malformed tool names in pending parts#29156
divitkashyap wants to merge 1 commit into
anomalyco:devfrom
divitkashyap:fix/pending-tool-malformed-28989

Conversation

@divitkashyap
Copy link
Copy Markdown

Issue for this PR

Closes #28989

Type of change

  • Bug fix

What does this PR do?

There's a race in tool-call ingestion: tool-input-start (processor.ts ensureToolCall) writes the part to SQLite with tool: input.name and status: "pending" before experimental_repairToolCall runs. When the model emits a malformed name like functions.bash<|tool_call|>, the bad name persists. On every following turn, toModelMessages re-serializes it as tool-${part.tool}, Bedrock rejects the name with a non-retryable ValidationException, and the session is dead forever.

Fix is at the serialization boundary in message-v2.ts: if a pending/running part's name fails /^[a-zA-Z0-9_-]+$/, emit a text hint instead of a dangling tool_use block. For completed/error parts (where this shouldn't normally happen) fall back to the invalid sentinel so the call still pairs with a result.

I went with the serialization-side fix over plumbing the repair callback into the processor or deferring the DB write, because the bad name is already sitting in many users' DBs — sanitizing on read means those sessions self-heal on the next turn instead of staying bricked until the user manually edits SQLite.

How did you verify your code works?

  • Added test("sanitizes malformed pending tool names to avoid Bedrock ValidationException") in message-v2.test.ts using the exact malformed name from the issue. Confirmed it fails on dev and passes with the fix.
  • bunx tsc --noEmit in packages/opencode is clean.
  • Full message-v2.test.ts suite: 37/37 pass.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

Closes anomalyco#28989

`tool-input-start` writes a tool part to SQLite with the raw stream name
before `experimental_repairToolCall` runs, so when the model emits a
malformed name (e.g. `functions.bash<|tool_call|>`) it lands in the DB
with status `pending`. On every subsequent turn `toModelMessages`
re-serializes that part as `tool-${part.tool}`; Bedrock rejects the
name with a non-retryable ValidationException and the session dies
permanently.

Sanitize at the serialization boundary: if a pending/running part has a
name that fails `/^[a-zA-Z0-9_-]+$/`, emit a text hint instead of a
dangling `tool_use`. For completed/error parts (where the bad name
shouldn't normally occur) fall back to the `invalid` sentinel.

Fixing on the serialization side also self-heals existing poisoned
sessions in users' DBs on the next launch.
@divitkashyap
Copy link
Copy Markdown
Author

@thdxr if you have a sec — this self-heals existing broken sessions on the next turn (no manual SQLite surgery needed for users already hit by it).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unknown tool name written to DB before repair callback fires; stuck pending part permanently kills session on Bedrock (400 toolUse.name constraint)

1 participant