ToolSearch generates tool_reference blocks on Bedrock despite API incompatibility

Resolved 💬 3 comments Opened Feb 12, 2026 by jonathanlaniado Closed Feb 15, 2026

Bug Description

When using Claude Code on AWS Bedrock (CLAUDE_CODE_USE_BEDROCK=1) with multiple MCP servers, ToolSearch activates and generates tool_reference content blocks that Bedrock's API rejects with 400 Tool reference not found in available tools.

The root cause is a mismatch between two code paths: the client-side ToolSearch enablement (which does NOT check provider) and the beta header routing (which deliberately excludes tool-search-tool-2025-10-19 from the SDK betas parameter for Bedrock).

Environment

  • Claude Code version: 2.1.38 / 2.1.39
  • Platform: macOS (darwin), ARM64
  • Provider: AWS Bedrock (CLAUDE_CODE_USE_BEDROCK=1, us-east-1)
  • Model: us.anthropic.claude-opus-4-6-v1
  • MCP servers: 6 servers (Sentry, Airtable, Notion, Linear, Context7, Playwright) → 105 tools, ~54,462 tokens of descriptions

Reproduction Steps

  1. Configure 6+ MCP servers in ~/.claude.json (enough to exceed 10% of context window in tool description tokens)
  2. Set CLAUDE_CODE_USE_BEDROCK=1 with valid AWS credentials
  3. Start a Claude Code session
  4. Ask Claude to use ToolSearch (or let it trigger automatically via deferred tool loading)
  5. ToolSearch returns tool_reference content blocks
  6. Next API request to Bedrock includes these blocks → 400 error

Error Message

API Error: 400 Tool reference 'mcp__linear__list_milestones' not found in available tools

Root Cause Analysis (Binary-Level)

The dual beta path

Claude Code has two separate beta-handling paths for Bedrock:

  1. VI() (SDK betas parameter): Filters OUT items in the eMR set for Bedrock. tool-search-tool-2025-10-19 is in eMR, so it's excluded from the SDK betas array.
  1. bnR()T2R() (request body anthropic_beta): Returns ONLY items IN eMR for Bedrock. tool-search-tool-2025-10-19 IS included and sent via the request body's anthropic_beta field.
// eMR — betas routed through body instead of SDK param for Bedrock
eMR = new Set([
  "interleaved-thinking-2025-05-14",
  "context-1m-2025-08-07",
  "tool-search-tool-2025-10-19",    // ← tool search beta
  "tool-examples-2025-10-29"
])

// VI() — SDK betas, filters out eMR for Bedrock
VI = memoize((model) => {
  let betas = PnR(model);
  if (NB() === "bedrock") return betas.filter(b => !eMR.has(b));
  return betas;
})

// bnR() — Bedrock body betas, returns only eMR items
bnR = memoize((model) => PnR(model).filter(b => eMR.has(b)))

The ToolSearch enablement gate does NOT check provider

ccT() (isToolSearchEnabled) checks:

  • HT0(model) — model blocklist (only blocks "haiku")
  • qT0(tools) — ToolSearchTool in tool list
  • A$T()ENABLE_TOOL_SEARCH env var mode
  • CP8() — token/character threshold (10% of context)

None of these check the provider. So on Bedrock with Opus, when MCP tool tokens exceed the 10% threshold, ccT() returns true → tools get defer_loading: true → ToolSearch activates → tool_reference blocks are generated.

The explicit Bedrock guard in esB

let G = q ? bH9() : null;  // bH9() returns "tool-search-tool-2025-10-19" for Bedrock
if (G && NB() !== "bedrock") {  // ← explicit Bedrock block
    if (!H.includes(G)) H.push(G);
}

This prevents the tool-search beta from entering the SDK betas array (H) for Bedrock. But G IS added to JT for the body path:

JT = NB() === "bedrock" ? [...bnR(model), ...G ? [G] : []] : [];
hT = T2R(JT);  // → anthropic_beta in request body

The result

  • Client-side: ToolSearch is enabled, tools are deferred, tool_reference blocks are generated
  • SDK betas: Does NOT include tool-search-tool-2025-10-19
  • Request body anthropic_beta: DOES include tool-search-tool-2025-10-19
  • API response: 400 — the SDK or API rejects tool_reference blocks because the SDK betas parameter doesn't signal support

Debug log evidence

[DEBUG] [ToolSearch:optimistic] mode=tst-auto, ENABLE_TOOL_SEARCH=undefined, result=true
[DEBUG] Auto tool search enabled: 54462 tokens (threshold: 20000, 10% of context) [source: query]
[DEBUG] Dynamic tool loading: 0/105 deferred tools included

ToolSearch activates, all 105 MCP tools are deferred, but 0 are ever successfully loaded — every attempt produces the 400 error.

Current Workaround

Setting ENABLE_TOOL_SEARCH=auto:30 raises the deferral threshold to 30% of context (60,000 tokens). Since our 54,462 tokens of MCP tool descriptions are below this, ToolSearch auto-disables and all tools load inline. No tool_reference blocks are generated, no 400 errors occur.

The tradeoff is ~54K tokens of tool descriptions consumed per request (~27% of the 200K context window).

Suggested Fix

The code should either:

  1. Check provider in ccT(): If on Bedrock and the SDK betas don't include the tool-search beta, disable deferral to prevent tool_reference generation.
  1. Add graceful degradation in mapToolResultToToolResultBlockParam: When provider is Bedrock, convert tool_reference blocks to text equivalents (similar to what WuA already does) instead of passing them raw:
// Pseudocode for graceful degradation
mapToolResultToToolResultBlockParam(T, R) {
  if (T.matches.length === 0)
    return { type: "tool_result", tool_use_id: R, content: "No matching deferred tools found" };
  if (NB() === "bedrock") {
    return { type: "tool_result", tool_use_id: R,
      content: T.matches.map(A => ({ type: "text", text: `[Loaded tool: ${A}]` })) };
  }
  return { type: "tool_result", tool_use_id: R,
    content: T.matches.map(A => ({ type: "tool_reference", tool_name: A })) };
}
  1. Remove tool-search-tool-2025-10-19 from eMR: If Bedrock InvokeModel now supports this beta (AWS docs suggest it does), removing it from the exclusion set would let the standard code path work.

Related Issues

  • #24591 — Context compaction fails with multiple subagents (same version, similar error pattern)
  • #23490 — MCP tools not available due to tool search experiment (tengu_tst_kx7)
  • #22893 — DISABLE_EXPERIMENTAL_BETAS doesn't suppress advanced-tool-use header
  • #12164 — MCP servers connect but tools not exposed to assistant

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗