Files
orca/src/shared/codex-auth-errors.ts
T
Brennan Benson ba5fa7d909 Classify Codex app-server chatgpt-auth-required as an auth error (#8765)
* Classify Codex app-server chatgpt-auth-required as an auth error

When auth.json holds only an OPENAI_API_KEY (no ChatGPT tokens), the
app-server RPC rejects account/rateLimits/read with "chatgpt
authentication required to read rate limits". That string matched none
of CODEX_AUTH_ERROR_PATTERNS, so fetchCodexRateLimits fell through to
the hidden PTY /status probe, which cannot render usage for such
accounts and burned the full 15s PTY timeout on every refresh cycle,
surfacing as a permanent "Refresh failed — PTY timeout" status chip.

Classify the message as an auth error so the RPC result is returned
directly (fast, accurate) and no PTY is spawned.

* Keep auth-required usage errors from rendering as a rate-limit Limited label

The new Codex app-server error 'chatgpt authentication required to read
rate limits' mentions rate limits only as the object it failed to read,
but the status bar's rate-limit classifier matched the phrase and
labeled the chip 'Limited'. Classify authentication-required messages as
auth failures so they get the standard softened refresh copy instead.
2026-07-14 15:28:04 -07:00

69 lines
2.1 KiB
TypeScript

const CODEX_AUTH_ERROR_PATTERNS = [
/access token could not be refreshed/i,
/authentication session could not be refreshed/i,
/refresh token (?:has expired|was already used|was revoked)/i,
/you have since logged out or signed in to another account/i,
/please (?:log out and )?sign in again/i,
/please reauthenticate/i,
/not logged in/i,
/token data is not available/i,
/auth (?:is missing|tokens are missing|does not expose)/i,
// Why: app-server rejects account/rateLimits/read with this when auth.json
// holds only an API key; without classification the fetcher falls through to
// a hidden PTY probe that can only time out (15s) on every refresh.
/chatgpt authentication required/i
]
const ANSI_ESCAPE_RE = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*[a-zA-Z]`, 'g')
export function isCodexAuthError(error: string | null | undefined): boolean {
const message = error?.trim()
if (!message) {
return false
}
return CODEX_AUTH_ERROR_PATTERNS.some((pattern) => pattern.test(message))
}
export function extractCodexAuthError(output: string | null | undefined): string | null {
if (!output) {
return null
}
let cleanPrefix = ''
for (const rawLine of iterateCodexOutputLines(output)) {
const line = rawLine.replace(ANSI_ESCAPE_RE, '').trim()
if (!line) {
continue
}
if (isCodexAuthError(line)) {
return line.slice(0, 4_000)
}
if (cleanPrefix.length < 4_000) {
cleanPrefix = cleanPrefix ? `${cleanPrefix}\n${line}` : line
cleanPrefix = cleanPrefix.slice(0, 4_000)
}
}
return isCodexAuthError(cleanPrefix) ? cleanPrefix : null
}
function* iterateCodexOutputLines(output: string): Generator<string> {
let lineStart = 0
for (let index = 0; index < output.length; index++) {
const code = output.charCodeAt(index)
if (code !== 10 && code !== 13) {
continue
}
yield output.slice(lineStart, index)
if (code === 13 && output.charCodeAt(index + 1) === 10) {
index++
}
lineStart = index + 1
}
if (lineStart <= output.length) {
yield output.slice(lineStart)
}
}