Files
orca/src/shared/codex-auth-errors.ts
T
Brennan Benson 59c05d32b0 fix(rate-limits): stop driving a hidden Codex TUI to read usage (#23806)
* fix(rate-limits): stop driving a hidden Codex TUI to read usage

When the headless Codex usage call failed, Orca opened a hidden
interactive Codex, typed /status and pressed Enter without reading the
screen, then killed it after 15 s. If Codex showed its "Update available"
prompt, that Enter picked "Update now", Codex started its installer, and
the 15 s kill interrupted it, leaving the global install broken.

Drop the hidden-terminal fallback. When the headless call fails with a
non-sign-in error, read the same usage from the HTTP endpoint Orca
already calls on WSL and for the 5-hour window, so a usage check can no
longer answer any Codex startup screen.

Fixes #17415

* test(rate-limits): pin the RPC error when the Codex HTTP fallback fails

Also drop comments that still described the removed hidden-terminal fallback.

* test(rate-limits): use real Response objects in Codex fetcher tests

The changed-code gate rejects the new type assertions this PR added.
2026-09-30 01:04:43 -07:00

75 lines
2.4 KiB
TypeScript

/** Carries the sign-in link of an in-flight `codex login` from main to every window. */
export const CODEX_PENDING_LOGIN_URL_CHANGED_CHANNEL = 'codexAccounts:pendingLoginUrlChanged'
/** The rejection a cancelled `codex login` produces; the Accounts pane reads it to keep a cancellation out of the error toast. */
export const CODEX_LOGIN_CANCELLED_MESSAGE = 'Codex sign-in was cancelled.'
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,
/sign in with chatgpt/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; that home has no ChatGPT token, so a fallback read cannot succeed.
/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)
}
}