Repository navigation
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
Description
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:
-
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 body403 permission_error "Authentication failed. Please check your credentials.". Because clients key onpermission_error/authentication_failed, Claude Code turns this intoPlease run /login · API Error: 403 ...— a login prompt for a key that is fine and never stopped working for other models. -
thinking.type.enabledis rejected with a nested, double-encoded error. Clients that still send the pre-5.5 thinking shape get400whosemessageis 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_PLANfor plan gates is already correct and should not regress;401 authentication_errorshould stay reserved for real credential problems. - The
thinking.type.enabledrejection should return the inner message as a flatmessagestring (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: CAPTUREDin 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
- On a GOAT plan,
POST /provider/v1/messageswithmodel=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."}} - Same key,
model=claude-sonnet-5→403 MODEL_NOT_IN_PLAN(auth passed; plan gate reported correctly). - Same key,
model=deepseek/deepseek-v4.1-flashvia/provider/v1/chat/completions→200. - Point Claude Code at the Provider API and send one message while the egress hits the refused colo → the client shows
Please run /login, persistsauthentication_failed. - Send
thinking: {"type":"enabled","budget_tokens":2048}toclaude-sonnet-5-5→ the double-encoded400above.
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.
-
Region refusals must not be
authentication_failed.
Location: whatever maps the upstream Anthropic refusal into/provider/v1/messagesresponses — today it collapses to403 permission_error "Authentication failed. Please check your credentials.".
Correct: return a distinctcode(e.g.MODEL_NOT_AVAILABLE_IN_REGION) and a message naming the model. KeepMODEL_NOT_IN_PLANfor plan gates and401 authentication_errorfor 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. -
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 innermessageis being stringified into the outermessage. Flatten it somessageis 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