Skip to content

fix(bedrock): filter out custom field from tools to prevent 400 errors#22861

Merged
ishaan-jaff merged 2 commits into
BerriAI:mainfrom
weiguangli-io:codex/litellm-22847-bedrock-custom-defer
Mar 5, 2026
Merged

fix(bedrock): filter out custom field from tools to prevent 400 errors#22861
ishaan-jaff merged 2 commits into
BerriAI:mainfrom
weiguangli-io:codex/litellm-22847-bedrock-custom-defer

Conversation

@weiguangli-io

Copy link
Copy Markdown
Contributor

Summary

Fixes #22847

Claude Code v2.1.69+ sends custom: {defer_loading: true} on tool definitions in the Anthropic /v1/messages request. Anthropic's API accepts this field, but Bedrock rejects it with:

tools.0.custom.defer_loading: Extra inputs are not permitted

This causes ~90% of requests to fail when proxying Claude Code through LiteLLM to Bedrock.

Changes

  • Added remove_custom_field_from_tools() utility in litellm/llms/bedrock/common_utils.py that strips the custom field from each tool dict in the request body
  • Applied the filter in both Bedrock invoke paths:
    • Messages API (litellm/llms/bedrock/messages/invoke_transformations/anthropic_claude3_transformation.py) — step 5a in the Bedrock-specific transformation pipeline, alongside existing output_format / stream / model pop logic
    • Chat API (litellm/llms/bedrock/chat/invoke_transformations/anthropic_claude3_transformation.py) — after output_format pop, before beta header injection

Test plan

  • Added test_remove_custom_field_from_tools covering:
    • Tools with custom field → field is removed, other fields preserved
    • Request without tools key → no error
    • Empty tools list → no error
    • tools: None → no error
  • All existing tests pass (pytest tests/test_litellm/llms/bedrock/messages/invoke_transformations/ — 4/4 passed)
  • Ruff lint passes on all changed files

Claude Code v2.1.69+ sends `custom: {defer_loading: true}` on tool
definitions. Anthropic's API accepts this field, but Bedrock rejects it
with "Extra inputs are not permitted", causing ~90% of requests to fail.

Strip the `custom` field from each tool in the request body before
sending to Bedrock, in both the Messages API and Chat API invoke paths.

Fixes BerriAI#22847
@vercel

vercel Bot commented Mar 5, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
litellm Ready Ready Preview, Comment Mar 5, 2026 9:55pm

Request Review

@greptile-apps

greptile-apps Bot commented Mar 5, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a regression where Claude Code v2.1.69+ began sending a custom: {defer_loading: true} field on tool definitions. Anthropic's own API accepts this field, but AWS Bedrock rejects it with "Extra inputs are not permitted", causing ~90 % of proxied requests to fail. The fix introduces a small utility remove_custom_field_from_tools() in common_utils.py and calls it in both Bedrock invoke code paths (Chat API and Messages API) immediately before the request body is dispatched to Bedrock.

  • litellm/llms/bedrock/common_utils.py — new remove_custom_field_from_tools() strips the top-level custom key from every tool dict in-place; handles None, empty list, and non-list values safely.
  • litellm/llms/bedrock/messages/invoke_transformations/anthropic_claude3_transformation.py — step 5a calls the utility after output_format conversion, before beta-header injection; feature-detection (tool_search_used, etc.) correctly continues to read from anthropic_messages_optional_request_params, which is unaffected.
  • litellm/llms/bedrock/chat/invoke_transformations/anthropic_claude3_transformation.py — mirrors the messages path; strip happens after model/stream/output_format pops; beta-header feature-detection reads from optional_params, also unaffected.
  • Tests — four unit cases for the utility function (happy path, missing key, empty list, None); no network calls, consistent with the mock-only test policy for this directory.

Minor observations:

  • A missing blank line exists between test_remove_custom_field_from_tools and test_remove_scope_from_cache_control in the test file (PEP 8 / ruff E302).
  • There is no integration-level test for the chat invoke path (AmazonAnthropicClaudeConfig.transform_request) with a custom-bearing tool; only the messages path and the isolated utility are tested.

Confidence Score: 4/5

  • Safe to merge; the fix is targeted, non-breaking, and well-tested for the core utility and messages path.
  • The change is small and surgical — it only removes a field that Bedrock actively rejects today. Both critical Bedrock invoke paths are covered. The utility handles all edge cases correctly. The only gaps are a missing blank line in the test file and the absence of a chat-path integration test, neither of which affects correctness.
  • litellm/llms/bedrock/chat/invoke_transformations/anthropic_claude3_transformation.py — no dedicated test exercises this code path end-to-end with a custom-bearing tool.

Important Files Changed

Filename Overview
litellm/llms/bedrock/common_utils.py Adds remove_custom_field_from_tools() utility that strips the top-level custom key from each tool dict in-place; handles None, empty list, and non-list gracefully.
litellm/llms/bedrock/chat/invoke_transformations/anthropic_claude3_transformation.py Calls remove_custom_field_from_tools on the assembled Anthropic request body after the model/stream/output_format pops; placement is correct and feature-detection still reads from optional_params (unaffected).
litellm/llms/bedrock/messages/invoke_transformations/anthropic_claude3_transformation.py Mirrors the chat-path change; remove_custom_field_from_tools is inserted as step 5a between the output_format conversion and the beta-header auto-injection; beta/feature detection reads from anthropic_messages_optional_request_params so it is unaffected by the strip.
tests/test_litellm/llws/bedrock/messages/invoke_transformations/test_anthropic_claude3_transformation.py Adds four unit-test cases for remove_custom_field_from_tools covering the happy path, missing key, empty list, and None value; all are mock-only with no network calls.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Incoming request\nwith tools incl. custom field] --> B{Bedrock invoke path?}
    B -- Chat API --> C[AmazonAnthropicClaudeConfig\n.transform_request]
    B -- Messages API --> D[AmazonAnthropicClaudeMessagesConfig\n.transform_request]
    C --> E[AnthropicConfig.transform_request\nbuilds _anthropic_request]
    E --> F[pop model / stream / output_format]
    F --> G[remove_custom_field_from_tools\n_anthropic_request]
    D --> H[AnthropicMessagesConfig.transform_request\nbuilds anthropic_messages_request]
    H --> I[pop stream / model]
    I --> J[_remove_ttl_from_cache_control]
    J --> K[_convert_output_format_to_inline_schema]
    K --> L[remove_custom_field_from_tools\nanthropic_messages_request]
    G --> M[Beta-header injection\nreads from optional_params — unaffected]
    L --> N[Beta-header injection\nreads from optional_request_params — unaffected]
    M --> O[Request sent to Bedrock\nno custom field → 200 OK]
    N --> O
Loading

Last reviewed commit: dedc50d

@ishaan-jaff ishaan-jaff merged commit 3d027c0 into BerriAI:main Mar 5, 2026
25 of 37 checks passed
Comment on lines +233 to 235
assert request4["tools"] is None

def test_remove_scope_from_cache_control():

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Missing blank line between top-level test functions

PEP 8 (and ruff's E302) requires two blank lines between top-level function definitions. There is only one blank line between the end of test_remove_custom_field_from_tools and the start of test_remove_scope_from_cache_control.

Suggested change
assert request4["tools"] is None
def test_remove_scope_from_cache_control():
assert request4["tools"] is None
def test_remove_scope_from_cache_control():

Comment on lines +114 to +118
# Remove `custom` field from tools (Bedrock doesn't support it)
# Claude Code sends `custom: {defer_loading: true}` on tool definitions,
# which causes Bedrock to reject the request with "Extra inputs are not permitted"
# Ref: https://github.com/BerriAI/litellm/issues/22847
remove_custom_field_from_tools(_anthropic_request)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No test coverage for the chat invoke path

The PR adds a test for remove_custom_field_from_tools in isolation and exercises it through the messages invoke transformation, but there is no test that exercises the chat invoke path (AmazonAnthropicClaudeConfig.transform_request) with a request that carries a custom field on tools.

Because the two transformation classes are separate, a future refactor could accidentally drop or mis-order this call without any test catching it. Consider adding a parallel test (analogous to the messages path) that calls AmazonAnthropicClaudeConfig.transform_request with a tool carrying custom and asserts it is absent in the returned dict.

milan-berri pushed a commit to milan-berri/litellm that referenced this pull request Mar 9, 2026
…ors (BerriAI#22861)

Claude Code v2.1.69+ sends `custom: {defer_loading: true}` on tool
definitions. Anthropic's API accepts this field, but Bedrock rejects it
with "Extra inputs are not permitted", causing ~90% of requests to fail.

Strip the `custom` field from each tool in the request body before
sending to Bedrock, in both the Messages API and Chat API invoke paths.

Fixes BerriAI#22847

Co-authored-by: Ishaan Jaff <ishaanjaffer0324@gmail.com>
milan-berri pushed a commit to milan-berri/litellm that referenced this pull request Mar 10, 2026
…ors (BerriAI#22861)

Claude Code v2.1.69+ sends `custom: {defer_loading: true}` on tool
definitions. Anthropic's API accepts this field, but Bedrock rejects it
with "Extra inputs are not permitted", causing ~90% of requests to fail.

Strip the `custom` field from each tool in the request body before
sending to Bedrock, in both the Messages API and Chat API invoke paths.

Fixes BerriAI#22847

Co-authored-by: Ishaan Jaff <ishaanjaffer0324@gmail.com>
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.

[Bug]: "Extra inputs are not permitted" via Claude Code 2.1.69 on Bedrock due to custom.defer_loading

2 participants