Skip to content

fix: [AI-678] add stub tool definitions for historical tool_use blocks#703

Merged
anandgupta42 merged 3 commits intomainfrom
fix/678-tool-definition-mismatch
Apr 13, 2026
Merged

fix: [AI-678] add stub tool definitions for historical tool_use blocks#703
anandgupta42 merged 3 commits intomainfrom
fix/678-tool-definition-mismatch

Conversation

@anandgupta42
Copy link
Copy Markdown
Contributor

@anandgupta42 anandgupta42 commented Apr 13, 2026

What does this PR do?

Fixes the Anthropic API error "Requests with 'tool_use' and 'tool_result' blocks must include tool definition" that occurs when conversation history references tools no longer in the active set (e.g., after switching agents from Plan→Builder, MCP tools disconnecting, or tools filtered by permissions).

Replaces the previous LiteLLM-only _noop workaround with a general fix that:

  • Extracts all tool names from tool-call blocks in message history
  • Adds stub definitions for any tool names missing from the active tools set
  • Returns "tool no longer available" if the model attempts to call a historical stub

Type of change

  • Bug fix (non-breaking change which fixes an issue)

Issue for this PR

Closes #678

How did you verify your code works?

  • Added 5 unit tests for toolNamesFromMessages() covering: empty messages, no tool calls, extraction, dedup, tool-call vs tool-result distinction
  • All 15 tests in llm.test.ts pass (5 new + 10 existing)
  • Full local CI passes (bun run ci — unit tests + marker guard)
  • Multi-model code review (Claude + GPT 5.4 + DeepSeek V3.2) — approved with 1 round of convergence

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • New and existing unit tests pass locally with my changes

Summary by cubic

Prevents Anthropic validation errors by adding stub tool definitions for historical tool_use blocks when tools are no longer active. Replaces the LiteLLM-only _noop workaround with a provider-agnostic fix for AI-678.

  • Bug Fixes
    • Scans message history for tool names in tool-call and tool-result, then adds stubs for any missing in the active set.
    • Stubs return "This tool is no longer available. Please use an alternative approach." to avoid 400s when agents switch or MCP tools disconnect.
    • Uses Object.hasOwn() for safe presence checks; removes _noop and dead hasToolCalls(); adds tests for toolNamesFromMessages (extraction, dedup, results).

Written for commit f7fdcc8. Summary will update on new commits.

Summary by CodeRabbit

  • Bug Fixes

    • Replaced provider-specific logic with a generalized stub-tool mechanism that scans message history for referenced tool names and injects placeholders for any missing tools, ensuring referenced tools are available at runtime.
  • Tests

    • Added and updated tests to validate extraction and deduplication of tool names from message histories, covering text-only, call-only, result-only, and mixed scenarios.

The Anthropic API requires every `tool_use` block in message history to
have a matching tool definition. When agents switch (Plan→Builder), MCP
tools disconnect, or tools are filtered by permissions, the history may
reference tools absent from the current set — causing a 400 error:
"Requests with 'tool_use' and 'tool_result' blocks must include tool
definition."

Replace the LiteLLM-only `_noop` workaround with a general fix:
- Extract all tool names from `tool-call` blocks in message history
- Add stub definitions for any names missing from the active tools set
- Stubs return "tool no longer available" if the model attempts to call them

Closes #678

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@claude claude bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.

Tip: disable this comment in your organization's Code Review settings.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 13, 2026

📝 Walkthrough

Walkthrough

Replaced provider-specific noop tool injection with a generic stub-tool mechanism that extracts referenced tool names from message history and injects stub definitions for missing tools. Replaced hasToolCalls with toolNamesFromMessages and updated tests accordingly.

Changes

Cohort / File(s) Summary
Core LLM logic
packages/opencode/src/session/llm.ts
Removed provider-specific _noop injection and hasToolCalls(messages): boolean. Added generalized parsing of input.messages to collect referenced toolNames and inject stub tool definitions for any missing tools. Exported toolNamesFromMessages(messages): Set<string> replaces previous helper.
Tests
packages/opencode/test/session/llm.test.ts
Replaced hasToolCalls tests with toolNamesFromMessages suite: validates empty input, non-tool messages, extraction from tool-call blocks, deduplication, and behavior with tool-result blocks.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 I hop through messages, noses twitching bright,
I sniff out names that hid from sight.
For every missing tool I find, a stub I sow,
So sessions hum and errors cease to grow.
— your rabbit dev, hopping on tiptoe 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically references the main change: adding stub tool definitions for historical tool_use blocks to fix issue AI-678.
Linked Issues check ✅ Passed The PR successfully addresses issue #678 by extracting tool names from historical tool-call/tool-result blocks and adding stub definitions for missing tools, preventing Anthropic API validation errors.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing issue #678: replaced provider-specific _noop workaround with generalized stub-tool mechanism, updated test suite, and removed unused hasToolCalls() function.
Description check ✅ Passed The pull request description is well-structured and addresses all major template requirements with detailed explanations of the problem, solution, and verification.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/678-tool-definition-mismatch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

… dead code

Multi-model review (GPT 5.4, Gemini 3.1 Pro) identified three issues:

- `toolNamesFromMessages()` now scans both `tool-call` AND `tool-result`
  blocks, guarding against orphaned tool-results in compacted histories
- Use `Object.hasOwn()` instead of direct property check to avoid
  prototype pollution edge case (`toString`, `constructor`)
- Remove dead `hasToolCalls()` function and its tests — sole call site
  was the LiteLLM workaround deleted in the previous commit

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/opencode/src/session/llm.ts`:
- Around line 287-295: The toolNamesFromMessages function currently only records
parts with type "tool-call"; update it so it also captures tool-result variants
(e.g., "tool-result" and "tool_result") by checking part.type for those values
and adding part.toolName (guarding that it exists) to the names set; modify the
loop in toolNamesFromMessages to include these additional type checks so
historical tool-result entries produce stubs just like tool-call entries.

In `@packages/opencode/test/session/llm.test.ts`:
- Around line 57-65: The test is asserting the old broken behavior by expecting
an empty set from LLM.toolNamesFromMessages for a message containing a
tool-result; update the test to expect the tool name to be returned instead
(i.e., the set should contain "bash") so LLM.toolNamesFromMessages returns new
Set(["bash"]) for the provided messages; locate the test in
packages/opencode/test/session/llm.test.ts and change the expect(...) assertion
accordingly to reflect the corrected behavior needed for the Anthropic
validation path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: f685342b-dede-4153-8a61-b16cd17739a2

📥 Commits

Reviewing files that changed from the base of the PR and between daaaceb and 6d44703.

📒 Files selected for processing (2)
  • packages/opencode/src/session/llm.ts
  • packages/opencode/test/session/llm.test.ts

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 2 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/opencode/src/session/llm.ts">

<violation number="1" location="packages/opencode/src/session/llm.ts:292">
P1: `tool-result` blocks also carry `toolName` and trigger the same Anthropic validation error when no matching tool definition exists. If history trimming leaves an orphaned `tool-result` without its paired `tool-call`, this scan will miss it and the API will still return a 400. Include `part.type === "tool-result"` in the condition.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.

for (const msg of messages) {
if (!Array.isArray(msg.content)) continue
for (const part of msg.content) {
if (part.type === "tool-call") names.add(part.toolName)
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: tool-result blocks also carry toolName and trigger the same Anthropic validation error when no matching tool definition exists. If history trimming leaves an orphaned tool-result without its paired tool-call, this scan will miss it and the API will still return a 400. Include part.type === "tool-result" in the condition.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/opencode/src/session/llm.ts, line 292:

<comment>`tool-result` blocks also carry `toolName` and trigger the same Anthropic validation error when no matching tool definition exists. If history trimming leaves an orphaned `tool-result` without its paired `tool-call`, this scan will miss it and the API will still return a 400. Include `part.type === "tool-result"` in the condition.</comment>

<file context>
@@ -276,4 +278,21 @@ export namespace LLM {
+    for (const msg of messages) {
+      if (!Array.isArray(msg.content)) continue
+      for (const part of msg.content) {
+        if (part.type === "tool-call") names.add(part.toolName)
+      }
+    }
</file context>
Suggested change
if (part.type === "tool-call") names.add(part.toolName)
if (part.type === "tool-call" || part.type === "tool-result") {
names.add(part.toolName)
}
Fix with Cubic

@anandgupta42 anandgupta42 merged commit a18ecc6 into main Apr 13, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Requests with 'tool_use' and 'tool_result' blocks must include tool definition.

1 participant