litellm - 💡(How to fix) Fix [Bug]: Claude Code 3P tab in Claude Desktop fails proxy auth — likely Authorization header (Anthropic OAuth token) takes precedence over user's x-api-key LiteLLM virtual key

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…

Error Message

litellm.proxy.proxy_server.user_api_key_auth(): Exception occurred - ProxyException: Authentication Error, Invalid proxy server token passed. Received API Key = sk-a***-oat..., Key Hash (Token) =<sha256(sk-ant-oat-...)>. Unable to find token in cache or LiteLLM_VerificationTokenTable HTTP 401 Unauthorized

Root Cause

Note on root cause: The LiteLLM-side behavior described below is verified by reading the code on main and v1.86.0. The assumption that Claude Desktop's Code 3P tab sends Authorization: Bearer sk-ant-oat-... alongside x-api-key: sk-<litellm-key> is a strong hypothesis but has not been confirmed by capturing the actual request headers. It fits the symptoms (CLI and Cowork tab work with the same key; the error reports a token-not-found rather than a malformed key) but a maintainer or reporter should validate by capturing the headers Claude Code 3P actually sends before designing the fix.

Code Example

curl -X POST http://<litellm-proxy>/v1/messages \
  -H "Authorization: Bearer sk-ant-oat-01-EXAMPLE-OAUTH-TOKEN" \
  -H "x-api-key: sk-<your-valid-litellm-virtual-key>" \
  -H "anthropic-version: 2023-06-01" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-sonnet-4-5",
    "max_tokens": 64,
    "messages": [{"role": "user", "content": "ping"}]
  }'

---

litellm.proxy.proxy_server.user_api_key_auth(): Exception occurred -
ProxyException: Authentication Error, Invalid proxy server token passed.
Received API Key = sk-a***-oat..., Key Hash (Token) =<sha256(sk-ant-oat-...)>.
Unable to find token in cache or `LiteLLM_VerificationTokenTable`
HTTP 401 Unauthorized

---

elif isinstance(api_key, str) and len(api_key) > 0:
    stripped = _get_bearer_token(api_key=api_key)
    if is_anthropic_oauth_key(stripped) and isinstance(anthropic_api_key_header, str):
        # Authorization carries an upstream Anthropic OAuth token, not the
        # LiteLLM virtual key — let it pass through and use x-api-key for
        # proxy auth instead.
        passed_in_key = anthropic_api_key_header
        api_key = anthropic_api_key_header
    else:
        passed_in_key = api_key
        api_key = stripped
RAW_BUFFERClick to expand / collapse

Note on root cause: The LiteLLM-side behavior described below is verified by reading the code on main and v1.86.0. The assumption that Claude Desktop's Code 3P tab sends Authorization: Bearer sk-ant-oat-... alongside x-api-key: sk-<litellm-key> is a strong hypothesis but has not been confirmed by capturing the actual request headers. It fits the symptoms (CLI and Cowork tab work with the same key; the error reports a token-not-found rather than a malformed key) but a maintainer or reporter should validate by capturing the headers Claude Code 3P actually sends before designing the fix.

Check for existing issues

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

Closest existing items, but none cover this specific bug:

  • #24436 / #24454 / #24539 — same OAuth-token-collision family, but those are about the outbound headers LiteLLM forwards to the upstream Anthropic API. This bug is in the inbound proxy auth itself (user_api_key_auth), which fails before any upstream call is made.
  • #24680 — same surface error message ("Unable to find token in cache or LiteLLM_VerificationTokenTable"), but unrelated cause (key cache regression in v1.82.3).

What happened?

Users authenticating from the Claude Code 3P (Third-Party API) tab in Claude Desktop receive an authentication failure even when their LiteLLM virtual key is valid:

Authentication failed Sign in again to continue.

Failed to authenticate. API Error: 401 Authentication Error, Invalid proxy server token passed. Unable to find token in cache or LiteLLM_VerificationTokenTable

The same LiteLLM virtual key works correctly in:

  • Claude Code CLI (claude command)
  • Claude Cowork 3P tab in Claude Desktop

It only fails in the Claude Code 3P tab in Claude Desktop.

Likely root cause (LiteLLM side — verified; client side — inferred)

Verified from the LiteLLM source: in litellm/proxy/auth/user_api_key_auth.pyget_api_key(), the precedence order is:

  1. x-litellm-api-key
  2. Authorizationwins if present
  3. API-Key (Azure)
  4. x-api-key (Anthropic)

If Authorization carries any sk-...-shaped value, _get_bearer_token strips Bearer , the value gets hashed and looked up in LiteLLM_VerificationTokenTable, and a miss produces exactly this 401 at litellm/proxy/auth/user_api_key_auth.py:1314. The Anthropic x-api-key header is never inspected when Authorization is non-empty. There is no special-case in the auth layer for Anthropic OAuth tokens.

Inferred (not yet confirmed) about Claude Desktop: the most plausible explanation for the symptoms is that the Claude Code 3P tab sends both:

  • Authorization: Bearer sk-ant-oat-... — the Desktop session's logged-in Anthropic OAuth token (an upstream credential, not a LiteLLM key)
  • x-api-key: sk-... — the user's LiteLLM virtual key entered in the 3P provider settings

This fits the error mode (token-not-found rather than malformed) and explains why the CLI and Cowork tab work with the same key. A header capture from the failing 3P tab would confirm.

Why the CLI and Cowork tab work (assuming the hypothesis)

  • The CLI sends only x-api-key with the LiteLLM key — no Authorization header — so the Anthropic-header branch is correctly used.
  • The Cowork tab apparently does not inject the Desktop OAuth token alongside the user's 3P key.

Notes

The codebase already recognizes that sk-ant-oat-* in an Authorization: Bearer header is a passthrough credential, not a proxy-auth credential — see is_anthropic_oauth_key() in litellm/llms/anthropic/common_utils.py:26 and the special-casing in litellm/proxy/litellm_pre_call_utils.py:589 and :2763. The proxy auth layer simply doesn't apply that same understanding when choosing which header to use as the LiteLLM key.

PRs #24454 and #24539 fix the closely-related outbound-header collision, but the inbound auth-precedence side of the bug remains.

Steps to Reproduce

You can reproduce the LiteLLM-side failure mode without Claude Desktop by simulating the headers the hypothesis says Claude Code 3P sends:

  1. Run a LiteLLM proxy with any valid virtual key (sk-...).
  2. Send a request that combines an Anthropic OAuth token in Authorization with the LiteLLM key in x-api-key:
curl -X POST http://<litellm-proxy>/v1/messages \
  -H "Authorization: Bearer sk-ant-oat-01-EXAMPLE-OAUTH-TOKEN" \
  -H "x-api-key: sk-<your-valid-litellm-virtual-key>" \
  -H "anthropic-version: 2023-06-01" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-sonnet-4-5",
    "max_tokens": 64,
    "messages": [{"role": "user", "content": "ping"}]
  }'
  1. Observe a 401 with the "Unable to find token in cache or LiteLLM_VerificationTokenTable" error, even though x-api-key is a valid LiteLLM virtual key.
  2. Remove the Authorization header — the same request with only x-api-key succeeds.

In Claude Desktop:

  1. Sign into Claude Desktop with an Anthropic account (this gives the Desktop client an OAuth session token).
  2. Open the Claude Code tab.
  3. Configure a Third-Party (3P) provider pointing at a LiteLLM proxy with a valid virtual key.
  4. Attempt to use Claude Code → "Authentication failed. Sign in again to continue." with the 401 above in the proxy logs.

The Claude Cowork tab and Claude Code CLI configured against the same proxy and key continue to work, isolating the issue to how Claude Code 3P composes its headers.

Relevant log output

litellm.proxy.proxy_server.user_api_key_auth(): Exception occurred -
ProxyException: Authentication Error, Invalid proxy server token passed.
Received API Key = sk-a***-oat..., Key Hash (Token) =<sha256(sk-ant-oat-...)>.
Unable to find token in cache or `LiteLLM_VerificationTokenTable`
HTTP 401 Unauthorized

What part of LiteLLM is this about?

Proxy

What LiteLLM version are you on?

v1.86.0 (also confirmed unfixed on main as of 2026-05-28 and on litellm_internal_staging / litellm_oss_staging_28_05_26)

Suggested fix (for maintainers)

Assuming the hypothesis holds: in litellm/proxy/auth/user_api_key_auth.pyget_api_key(), when the Authorization header carries an Anthropic OAuth token (sk-ant-oat-*), skip it for proxy auth and fall through to anthropic_api_key_header (x-api-key). The OAuth token is already understood elsewhere in the codebase as a pass-through credential for the upstream provider.

Conceptually:

elif isinstance(api_key, str) and len(api_key) > 0:
    stripped = _get_bearer_token(api_key=api_key)
    if is_anthropic_oauth_key(stripped) and isinstance(anthropic_api_key_header, str):
        # Authorization carries an upstream Anthropic OAuth token, not the
        # LiteLLM virtual key — let it pass through and use x-api-key for
        # proxy auth instead.
        passed_in_key = anthropic_api_key_header
        api_key = anthropic_api_key_header
    else:
        passed_in_key = api_key
        api_key = stripped

Test cases worth covering in tests/test_litellm/proxy/auth/test_user_api_key_auth.py:

  • Authorization only (no x-api-key) — existing behavior preserved.
  • x-api-key only — existing behavior preserved.
  • Both, with Authorization carrying an sk-ant-oat-* OAuth token — proxy auth uses x-api-key (new behavior).
  • Both, with Authorization carrying a regular LiteLLM key — proxy auth uses Authorization (existing behavior preserved).

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