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 件 担当者 0 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

まだ誰も着手していません。

評価

難易度
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分
マージ済み PR(30日)
7

環境構築

このプロジェクトには開発コンテナ、Dockerfile、コントリビューションガイドがありません。まず README を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

CommandCodeAI/command-code のほかの issue

CommandCodeAI/command-code の issue をすべて見る

似ている issue

Backend & API Design の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。