Skip to content

[BUG] Anthropic Messages route (/v1/messages) ignores x-headroom-base-url override #1760

Description

@adryanev

Description

The Anthropic Messages route (POST /v1/messages, providers/proxy_routes.py) does not honor the x-headroom-base-url per-request upstream override header. The OpenAI-compatible routes already read this header via _resolve_openai_upstream_base / x-headroom-original-path (proxy/handlers/openai.py), and the generic passthrough route honors it too (providers/proxy_routes.py:953), but handle_anthropic_messages is always called without upstream_base_url, so it unconditionally forwards to api.anthropic.com.

This breaks any client that speaks the Anthropic Messages wire format but authenticates against a different, non-Anthropic gateway — for example OpenCode's opencode-go provider (GLM-5.2 / MiniMax served through OpenCode Zen's "Go" tier, https://opencode.ai/zen/go). The client sends anthropic-version / x-api-key exactly like a native Anthropic client, headroom forwards it to api.anthropic.com, and the (Zen) API key is rejected there.

To Reproduce

  1. Point an Anthropic-Messages-format client at the headroom proxy with a per-request upstream override:
    curl -i http://127.0.0.1:8787/v1/messages \
      -H "content-type: application/json" \
      -H "anthropic-version: 2023-06-01" \
      -H "x-headroom-base-url: https://opencode.ai/zen/go" \
      -H "x-api-key: <a Zen Go API key>" \
      -d '{"model":"glm-5.2","max_tokens":16,"messages":[{"role":"user","content":"hi"}]}'
    
  2. Observe the proxy log: the request is forwarded to https://api.anthropic.com/v1/messages, not https://opencode.ai/zen/go/v1/messages.
  3. The upstream (real Anthropic) rejects the Zen key.

Expected Behavior

When x-headroom-base-url is present on a /v1/messages request, the proxy should forward to <x-headroom-base-url>/v1/messages, the same way the OpenAI-compatible handlers and the generic passthrough route already honor the header.

Actual Behavior

x-headroom-base-url is ignored on /v1/messages; the request always goes to the hardcoded ANTHROPIC_API_URL, and the response is:

{"type":"error","error":{"type":"authentication_error","message":"invalid x-api-key"}}

(the real Anthropic API rejecting a key meant for a different gateway).

Code Sample

# providers/proxy_routes.py — current
@app.post("/v1/messages")
async def anthropic_messages(request: Request):
    return await proxy.handle_anthropic_messages(request)

handle_anthropic_messages(request, upstream_base_url=None, ...) already supports an upstream_base_url override (proxy/handlers/anthropic.py) and, when passed, builds the upstream URL via build_copilot_upstream_url(upstream_base_url, request.url.path) — the same helper the Azure Anthropic routing path already reuses. The route just never passes it.

Local workaround (confirmed working) — read x-headroom-base-url in the route and pass it through:

@app.post("/v1/messages")
async def anthropic_messages(request: Request):
    custom_base = request.headers.get("x-headroom-base-url", "").strip()
    if custom_base:
        return await proxy.handle_anthropic_messages(request, upstream_base_url=custom_base)
    return await proxy.handle_anthropic_messages(request)

Error Output

HTTP/1.1 401
{"type":"error","error":{"type":"authentication_error","message":"invalid x-api-key"}}

Environment

  • Headroom version: 0.29.0 (pipx)
  • Python version: 3.12.13
  • OS: macOS 26.5.1
  • LLM Provider: OpenCode Zen "Go" tier (opencode-go provider, GLM-5.2 / MiniMax), Anthropic-Messages wire format

Additional Context

Related but distinct from #1503 (dedicated OpenAI handlers ignoring x-headroom-base-url) — this is the Anthropic Messages equivalent of that gap, and appears unreported. Also related to #1651 (which covers the OpenAI-compatible route, already fixed by #1596).

build_copilot_upstream_url is already a generic base+path concatenation helper reused for Vertex AI Anthropic routing (proxy_routes.py), so wiring it into /v1/messages should be a small, low-risk change consistent with the existing OpenAI-route pattern.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions