Skip to content

Provider API: region-restricted Claude 5.5 models return 403 "Authentication failed", which makes Claude Code prompt /login (and thinking.type.enabled 400 is double-encoded) #1037

Open

Description

@esp32gallery

Provider API: region-restricted Claude 5.5 models return 403 "Authentication failed", which makes Claude Code prompt /login; the same endpoint also 400s on thinking.type=enabled with a double-encoded message

No API keys, account ids, prompts, or org names are included below.

Summary

Two separate defects, both hit through POST https://api.commandcode.ai/provider/v1/messages with a GOAT plan:

  1. Region-restricted models surface as a credential error. For claude-sonnet-5-5 / claude-haiku-5-5, when the request is refused for region/edge reasons the API answers with the credential-shaped body 403 permission_error "Authentication failed. Please check your credentials.". Because clients key on permission_error / authentication_failed, Claude Code turns this into Please run /login · API Error: 403 ... — a login prompt for a key that is fine and never stopped working for other models.

  2. thinking.type.enabled is rejected with a nested, double-encoded error. Clients that still send the pre-5.5 thinking shape get 400 whose message is a stringified JSON document rather than a message, so it can't be pattern-matched or shown usefully.

This is the same class as #930 (region restriction mapped onto an auth error) and the same models as the Korea report in #945 (HKG edge 403 ×16 / NRT edge 200 ×5, same key, seconds apart). Filing separately because the client-visible harm here is the /login prompt, and because of the second defect.

Actual Behavior

1) Region-restricted refusal, credential-shaped

HTTP/2 403
{"type":"error","error":{"type":"permission_error","message":"Authentication failed. Please check your credentials."}}

What the client does with it (Claude Code 2.1.300, same key, same session):

Please run /login · API Error: 403 Command Code: Authentication failed. Please check your credentials.

...with the persisted record marked "error": "authentication_failed", "apiErrorStatus": 403. The user is told to re-authenticate a credential that is valid; re-login does not help, and the next model choice works.

2) thinking.type.enabled

POST /provider/v1/messages
{"model":"claude-sonnet-5-5","max_tokens":4096,
 "thinking":{"type":"enabled","budget_tokens":2048},
 "messages":[{"role":"user","content":"hi"}]}

HTTP/2 400
{"type":"error","error":{"type":"invalid_request_error","message":
 "{\"type\":\"error\",\"error\":{\"type\":\"invalid_request_error\",\"message\":
 \"\\\"thinking.type.enabled\\\" is not supported for this model. Use \\\"thinking.type.adaptive\\\" and \\\"output_config.effort\\\" to control thinking behavior.\"},
 \"request_id\":\"req_...\"}"}}

The text is correct but it arrives as an escaped JSON string inside message, so it is not message-shaped and not matchable. thinking.type=adaptive + output_config.effort returns 200.

Expected Behavior

  • A region/edge refusal should not reuse authentication_failed. A distinct code is enough, e.g.
    403 {"error":{"message":"claude-sonnet-5-5 is not available in your region.","type":"permission_error","code":"MODEL_NOT_AVAILABLE_IN_REGION"}}
    — MODEL_NOT_IN_PLAN for plan gates is already correct and should not regress; 401 authentication_error should stay reserved for real credential problems.
  • The thinking.type.enabled rejection should return the inner message as a flat message string (not stringified JSON).

Evidence

Region-shaped refusal — our account, 2026-10-16, all times CST (UTC+8)

Same key throughout; no config change; deepseek/deepseek-v4.1-flash on the same key and same endpoint was 31/31 × 200 in the same window, which is the control proving the key and the endpoint were healthy.

Window Attempts on 5.5 models 200 403 400
11:00:30 → 12:04:58 34 0 32 2
  • Last success before the window: 10:04:47
  • First success after the window: 19:11:12 (≈ 9h later, nothing changed on our side)
  • Clients hit: claude (Claude Code), curl, Python-urllib, pi — 4 different shapes, all 403, so it is not a header or body problem.
  • 403 latencies were 21–675 ms, i.e. rejected upstream, not our request being wrong.
  • 3 of them came back as 403 Command Code: CAPTURED in 21–29 ms (edge/WAF shape).

This also corrects one point in the support reply for our account: there were no claude-sonnet-5-5 successes interleaved with the refusals between 11:04 and 11:07 on this key — the run was a clean 34-attempt, zero-success window.

Region is the variable, not the account (reproducible shape)

Same key, same second, only the egress changes:

Egress Cloudflare loc Result
JP tunnel node JP / colo=KIX 200 ×N
CN residential (Shanghai) CN / colo=LAX 200 ×N

And in #945 a Korea user showed the same split on these exact two models: every cf-ray ending -HKG failed, every -NRT succeeded. So the refusal follows the edge the request lands on, which is consistent with the support reply — the problem is only that the wire format says "your credentials are bad".

Steps to reproduce

  1. On a GOAT plan, POST /provider/v1/messages with model=claude-sonnet-5-5, max_tokens: 16, one user message — from an egress whose Cloudflare colo is a refused one (e.g. HKG):
    → 403 {"type":"error","error":{"type":"permission_error","message":"Authentication failed. Please check your credentials."}}
  2. Same key, model=claude-sonnet-5 → 403 MODEL_NOT_IN_PLAN (auth passed; plan gate reported correctly).
  3. Same key, model=deepseek/deepseek-v4.1-flash via /provider/v1/chat/completions → 200.
  4. Point Claude Code at the Provider API and send one message while the egress hits the refused colo → the client shows Please run /login, persists authentication_failed.
  5. Send thinking: {"type":"enabled","budget_tokens":2048} to claude-sonnet-5-5 → the double-encoded 400 above.

Command Code Version

Not applicable — reproduced against the Provider API directly (curl), no cmd installed on the machine. Claude Code 2.1.300 was the affected client for defect 1.

Operating System

macOS

Terminal/IDE

zsh + curl 8.x; Claude Code 2.1.300

Shell

zsh

Session file (optional)

Not attached — it contains the whole session. The two lines that matter are quoted verbatim above.

Fix prompt (optional)

Two things to fix, both in the Provider API error path.

  1. Region refusals must not be authentication_failed.
    Location: whatever maps the upstream Anthropic refusal into /provider/v1/messages responses — today it collapses to 403 permission_error "Authentication failed. Please check your credentials.".
    Correct: return a distinct code (e.g. MODEL_NOT_AVAILABLE_IN_REGION) and a message naming the model. Keep MODEL_NOT_IN_PLAN for plan gates and 401 authentication_error for real credential failures. Evidence that the reason is known internally: the CLI already prints the region reason for other models (see [Provider API] Region-restricted models return 403 "Authentication failed" while the same API correctly returns MODEL_NOT_IN_PLAN for plan gates #930), and the API itself reports plan gates correctly on the same key.

  2. Stop double-encoding upstream invalid_request_errors.
    Location: the same mapping layer. When the upstream body is {"type":"error","error":{"type":"invalid_request_error","message":"..."}}, the inner message is being stringified into the outer message. Flatten it so message is the plain sentence.

How to check: from an egress that lands on a refused colo, curl the three model cases in the Evidence table — expect MODEL_NOT_AVAILABLE_IN_REGION, MODEL_NOT_IN_PLAN, 200 respectively; and thinking.type.enabled should return a flat invalid_request_error message.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions