litellm - 💡(How to fix) Fix [Bug]: workspace_id is sent in the Anthropic Messages request body (not just the header) on the bedrock/claude_platform chat-completions route

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

Root Cause

This affects the chat-completions surface only (litellm.completion(...) and the proxy /v1/chat/completions, which uses BedrockClaudePlatformConfigAnthropicConfig). The native messages surface (litellm.anthropic_messages(...) / /v1/messages, which uses BedrockClaudePlatformMessagesConfigAnthropicMessagesConfig) is not affected, because it builds the body through an explicit allow-list that excludes these keys.

Fix Action

Fix / Workaround

import json
from unittest.mock import patch

with patch("litellm.llms.custom_httpx.http_handler.HTTPHandler.post", mock_post):
    litellm.completion(
        model="bedrock/claude_platform/claude-opus-4-8",
        messages=[{"role": "user", "content": "hello"}],
        max_tokens=10,
        api_base="https://aws-external-anthropic.us-west-2.api.aws",
        api_key="fake-platform-key",
        workspace_id="wrkspc_test",
    )

Code Example

import json
from unittest.mock import patch

import httpx
import litellm

captured = {}


def mock_post(self, url, data=None, headers=None, **kwargs):
    raw = data.decode("utf-8") if isinstance(data, (bytes, bytearray)) else (data or "{}")
    captured["path"] = httpx.URL(url).path
    captured["headers"] = dict(headers or {})
    captured["body"] = json.loads(raw)
    return httpx.Response(
        status_code=200,
        json={
            "id": "msg_test",
            "type": "message",
            "role": "assistant",
            "model": "claude-opus-4-8",
            "content": [{"type": "text", "text": "ok"}],
            "stop_reason": "end_turn",
            "stop_sequence": None,
            "usage": {"input_tokens": 1, "output_tokens": 1},
        },
        request=httpx.Request("POST", url),
    )


with patch("litellm.llms.custom_httpx.http_handler.HTTPHandler.post", mock_post):
    litellm.completion(
        model="bedrock/claude_platform/claude-opus-4-8",
        messages=[{"role": "user", "content": "hello"}],
        max_tokens=10,
        api_base="https://aws-external-anthropic.us-west-2.api.aws",
        api_key="fake-platform-key",
        workspace_id="wrkspc_test",
    )

print("path:", captured["path"])
print("anthropic-workspace-id header:", captured["headers"].get("anthropic-workspace-id"))
print("request body:", json.dumps(captured["body"], indent=2))

# This assertion FAILS on v1.87.0 — demonstrating the bug:
assert "workspace_id" not in captured["body"], "BUG: workspace_id leaked into the /v1/messages request body"

---

model_list:
  - model_name: claude-platform-opus
    litellm_params:
      model: bedrock/claude_platform/claude-opus-4-8
      aws_region_name: us-east-1
      workspace_id: <your-workspace-id>
      api_key: <your-anthropic-on-aws-api-key>   # API-key mode; SigV4 mode leaks identically

---
RAW_BUFFERClick to expand / collapse

Check for existing issues

  • I have searched the existing issues and checked that my issue is not a duplicate.

What happened?

On the bedrock/claude_platform/<model> route, when a workspace_id is supplied (which is required for this route), the value is correctly set as the anthropic-workspace-id HTTP header but is ALSO serialized as a top-level workspace_id field inside the JSON body POSTed to …/v1/messages.

workspace_id is not a valid field of the Anthropic Messages API request body. Anthropic's /v1/messages rejects unknown top-level fields, so the gateway (aws-external-anthropic.<region>.api.aws) is expected to return a 400 invalid_request_error. The value ends up duplicated — once in the header (correct) and once in the body (incorrect).

This affects the chat-completions surface only (litellm.completion(...) and the proxy /v1/chat/completions, which uses BedrockClaudePlatformConfigAnthropicConfig). The native messages surface (litellm.anthropic_messages(...) / /v1/messages, which uses BedrockClaudePlatformMessagesConfigAnthropicMessagesConfig) is not affected, because it builds the body through an explicit allow-list that excludes these keys.

All three accepted workspace key spellings leak identically on the chat path: workspace_id, aws_workspace_id, anthropic_workspace_id.

Steps to Reproduce

No AWS credentials or network access required — the script below mocks the HTTP layer and inspects the exact outbound request body (same mocking approach as the provider's own unit tests).

import json
from unittest.mock import patch

import httpx
import litellm

captured = {}


def mock_post(self, url, data=None, headers=None, **kwargs):
    raw = data.decode("utf-8") if isinstance(data, (bytes, bytearray)) else (data or "{}")
    captured["path"] = httpx.URL(url).path
    captured["headers"] = dict(headers or {})
    captured["body"] = json.loads(raw)
    return httpx.Response(
        status_code=200,
        json={
            "id": "msg_test",
            "type": "message",
            "role": "assistant",
            "model": "claude-opus-4-8",
            "content": [{"type": "text", "text": "ok"}],
            "stop_reason": "end_turn",
            "stop_sequence": None,
            "usage": {"input_tokens": 1, "output_tokens": 1},
        },
        request=httpx.Request("POST", url),
    )


with patch("litellm.llms.custom_httpx.http_handler.HTTPHandler.post", mock_post):
    litellm.completion(
        model="bedrock/claude_platform/claude-opus-4-8",
        messages=[{"role": "user", "content": "hello"}],
        max_tokens=10,
        api_base="https://aws-external-anthropic.us-west-2.api.aws",
        api_key="fake-platform-key",
        workspace_id="wrkspc_test",
    )

print("path:", captured["path"])
print("anthropic-workspace-id header:", captured["headers"].get("anthropic-workspace-id"))
print("request body:", json.dumps(captured["body"], indent=2))

# This assertion FAILS on v1.87.0 — demonstrating the bug:
assert "workspace_id" not in captured["body"], "BUG: workspace_id leaked into the /v1/messages request body"

Equivalent proxy reproduction (config.yaml):

model_list:
  - model_name: claude-platform-opus
    litellm_params:
      model: bedrock/claude_platform/claude-opus-4-8
      aws_region_name: us-east-1
      workspace_id: <your-workspace-id>
      api_key: <your-anthropic-on-aws-api-key>   # API-key mode; SigV4 mode leaks identically

Then POST /v1/chat/completions with that model — the upstream /v1/messages body will contain a stray top-level workspace_id.

  1. Run the Python script above (or the proxy config + a chat request).
  2. Inspect the captured/outgoing request body to …/v1/messages.
  3. Observe that the body contains a top-level "workspace_id": "wrkspc_test" in addition to the (correct) anthropic-workspace-id header.

Relevant log output

What part of LiteLLM is this about?

SDK (litellm Python package)

What LiteLLM version are you on ?

1.87.0-rc.2

Twitter / LinkedIn details

No response

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

litellm - 💡(How to fix) Fix [Bug]: workspace_id is sent in the Anthropic Messages request body (not just the header) on the bedrock/claude_platform chat-completions route