Bug: pi-ai adapter misclassifies 401/403 in error text as AUTH, surfacing misleading "API key is invalid" #3073
heming-gmh
started this conversation in
General
Replies: 1 comment 1 reply
1 reply
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Summary
The pi-ai LLM adapter (
@deepseek-ai/dsh-llm-pi-ai) classifies a provider error as an authentication failure (code: "AUTH") whenever the flattened error message text contains a standalone401or403. The client runtime then renders everyAUTHfailure as the fixed string "API key is invalid" (@deepseek-ai/dsh-client-runtime,displayFailureMessage).As a result, non-authentication failures — most notably HTTP 403 authorization/permission errors (permission denied, region/policy restrictions, upstream "forbidden" / "Request not allowed"), and any error whose message merely contains the digits
401/403in an unrelated field (request id, nestedrawbody, routing metadata, counters) — are all shown to the user as "API key is invalid".This actively misleads users: it points them at their API key when the key is valid and the real cause is something else (authorization, provider routing, policy). The true diagnostic is preserved in the session log, but the surfaced copy sends users down the wrong path (e.g. rotating a perfectly valid key).
Where
packages/llm/llm-pi-ai/src/stream.ts—classifyPiAiError()packages/client/runtime—displayFailureMessage()Two distinct problems
1. Semantic: 403 is not authentication. HTTP 401 = unauthenticated (bad/missing credentials). HTTP 403 = authenticated but not authorized (permission, policy, region, model-not-allowed). Collapsing 403 into
AUTH— and then into "API key is invalid" — is wrong for the large class of 403s that have nothing to do with the key. The sibling adapter@deepseek-ai/dsh-llm-deepseek(httpErrorCode()) already keys off the structured integer status, but even there401 || 403 -> AUTHconflates authz with authn.2. Fragility: matching digits in free text.
/\b(?:401|403)\b/runs against the whole flattened message, so a401/403appearing in a request id, a nested providerrawbody, routing metadata, or a numeric counter is enough to force anAUTHclassification even when the response was not a 401/403 at all. The file's own header comment already documents that pi-ai flattens the error to a bare string and that classification is stuck pattern-matching text; this report is about tightening that text heuristic where it is demonstrably too broad.Reproduction (synthetic, no real data)
Feed
classifyPiAiErrora message that represents a 403 permission failure (not an auth failure). A representative synthetic message:Observed:
classifyPiAiError(msg) === "AUTH"-> UI shows "API key is invalid".Expected: a 403 permission/authorization failure should NOT be reported as an invalid API key; the key was valid.
A second, purely-incidental case (no real HTTP 401/403 involved at all):
Observed:
"AUTH"(the standalone403inside the trace id matches).Expected:
"SERVER".Suggested fix direction
Within the existing text-matching constraint (no upstream structured error yet):
AUTH. Route 403 to a distinct code (e.g.FORBIDDEN/PERMISSION) rather thanAUTH.AUTH.displayFailureMessage: for authorization failures use neutral copy such as "Authentication or authorization failed (see session log)" instead of the key-specific "API key is invalid", so a valid key is never wrongly implicated.A concrete patch implementing (1) and (2) in
classifyPiAiError, plus (3) indisplayFailureMessage, is attached separately.Notes
Suggested patch (for reference)
Click to expand the suggested diff
中文版 (Chinese translation)
概述
pi-ai LLM adapter(
@deepseek-ai/dsh-llm-pi-ai)在对错误分类时,只要被拍平的错误消息文本中出现独立的401或403,就会把该错误归类为鉴权失败(code: "AUTH")。随后 client runtime(@deepseek-ai/dsh-client-runtime的displayFailureMessage)会把所有AUTH失败统一渲染为固定文案 "API key is invalid"。后果是:非鉴权类的失败——尤其是 HTTP 403 授权/权限错误(权限不足、地域/策略限制、上游 "forbidden" / "Request not allowed"),以及任何消息里只是恰好包含数字
401/403(出现在 request id、嵌套的rawbody、路由 metadata、计数器等无关字段)的错误——全部会被展示给用户为 "API key is invalid"。这会主动误导用户:在 API key 明明有效、真实原因是别的(授权、上游路由、策略)时,把矛头指向 key。真实诊断信息虽然保留在 session log 中,但展示出来的文案会把用户引向错误方向(例如去轮换一个完全正常的 key)。
位置
packages/llm/llm-pi-ai/src/stream.ts—classifyPiAiError()packages/client/runtime—displayFailureMessage()两个层面的问题
1. 语义问题:403 不是鉴权失败。 HTTP 401 = 未认证(凭证错误/缺失);HTTP 403 = 已认证但未授权(权限、策略、地域、模型不可用)。把 403 塞进
AUTH、再变成 "API key is invalid",对那一大类与 key 无关的 403 来说是错误的。兄弟 adapter@deepseek-ai/dsh-llm-deepseek(httpErrorCode())已经基于结构化整型 status 判断,但即便如此,401 || 403 -> AUTH仍然把授权(authz)与认证(authn)混为一谈。2. 脆弱性:在自由文本里匹配数字。
/\b(?:401|403)\b/是对整条拍平后的消息做匹配,因此只要401/403出现在 request id、嵌套的上游rawbody、路由 metadata 或某个数字计数里,就足以强制归类为AUTH——哪怕这个响应根本不是 401/403。该文件自己的头部注释已经说明 pi-ai 会把错误拍平成裸字符串、分类只能做文本匹配;本报告要处理的正是这个文本启发式中明显过宽的部分。复现(合成数据,无任何真实信息)
给
classifyPiAiError喂一条代表 403 权限失败(而非鉴权失败)的消息。一个有代表性的合成消息:观察到:
classifyPiAiError(msg) === "AUTH"-> UI 显示 "API key is invalid"。期望:403 权限/授权失败不应被报告为 API key 无效;key 是有效的。
第二个纯属误伤的场景(完全不涉及真实的 HTTP 401/403):
观察到:
"AUTH"(trace id 里独立的403命中了)。期望:
"SERVER"。建议的修复方向
在现有文本匹配的约束下(上游暂时不提供结构化错误):
AUTH;把 403 路由到独立的 code(如FORBIDDEN/PERMISSION)而非AUTH。AUTH。displayFailureMessage):对授权类失败使用中性文案,如 "Authentication or authorization failed (see session log)",而不是针对 key 的 "API key is invalid",从而避免误伤一个有效的 key。一份实现 (1)(2) 于
classifyPiAiError、以及 (3) 于displayFailureMessage的具体 patch 另附。说明
All reactions