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)

未關閉
#1,037 0 則留言 0 個 reaction 已指派 0 人 在 GitHub 檢視

維護者通常 1 天內回覆

還沒有人認領這個 Issue。

評估

難度
4/5
預估耗時
3-5 天
新手友好度
38/100
Issue 類型
缺陷
描述清晰度
基本清楚
活躍度
活躍
領域
api, backend

研究方向

The report points to the error mapping for POST /provider/v1/messages but names no source files or tests. Start by locating that endpoint and tracing how upstream region refusals and invalid_request_error responses are converted. Done means region refusals have a distinct code while plan and credential errors retain their meanings, and the thinking.type=enabled error has a flat message.

由索引模型根據 Issue 內容生成。

描述

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 #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.

主要語言
沒有語言資料
星號
4.2k
分支
377
平均合併
1 天 17 分鐘
30 天內合併 PR
7

環境準備

這個專案沒有提供開發容器、Dockerfile 或貢獻指南,環境需要你自己搭建:先看它的 README,通用步驟見我們的新手貢獻指南。

從這裡開始

  1. 先讀完整個 Issue,再讀專案的貢獻指南。
  2. 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
  3. Fork 儲存庫,在一個分支上完成修改。
  4. 送出 Pull Request,並在描述裡引用這個 Issue 編號。

CommandCodeAI/command-code 的其他 Issue

查看 CommandCodeAI/command-code 的全部 Issue

相似的 Issue

更多 Backend & API Design Issue

把新 issue 寄到你的電子郵件信箱

精選適合新手參與的 GitHub issue 摘要。