mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-31 04:12:10 +03:00
fix(codex): treat OAuth 401 as unrecoverable refresh failure (#4686)
Integrated into release/v3.8.37 — codex OAuth 401 treated as unrecoverable refresh. Cherry-picked onto release tip; token-refresh-service.test.ts 38/38 green.
This commit is contained in:
committed by
GitHub
parent
d773bf7720
commit
a2ce88c8eb
@@ -1063,6 +1063,26 @@ export async function refreshCodexToken(refreshToken, log, proxyConfig: unknown
|
||||
return { error: "unrecoverable_refresh_error", code: errorCode };
|
||||
}
|
||||
|
||||
// Defense-in-depth (port from decolua/9router#1821): any 401 from OpenAI's
|
||||
// OAuth token endpoint means the refresh credential itself was rejected
|
||||
// (e.g. rotated away, or a payload variant whose code we do not yet
|
||||
// recognize — OpenAI has shipped both `token_expired` and the bare
|
||||
// "Could not validate your token" message). Retrying with the same dead
|
||||
// refresh token will never succeed; surface re-auth instead of looping.
|
||||
// 429 / 5xx remain transient and fall through to the retryable branch.
|
||||
if (response.status === 401) {
|
||||
const code = errorCode || "unauthorized";
|
||||
log?.error?.(
|
||||
"TOKEN_REFRESH",
|
||||
"Codex OAuth token endpoint returned 401. Re-authentication required.",
|
||||
{
|
||||
status: response.status,
|
||||
errorCode: code,
|
||||
}
|
||||
);
|
||||
return { error: "unrecoverable_refresh_error", code };
|
||||
}
|
||||
|
||||
log?.error?.("TOKEN_REFRESH", "Failed to refresh Codex token", {
|
||||
status: response.status,
|
||||
error: errorText,
|
||||
|
||||
@@ -438,6 +438,37 @@ test("refreshCodexToken recognizes refresh_token_reused responses", async () =>
|
||||
);
|
||||
});
|
||||
|
||||
// Port from decolua/9router#1821 (sacwooky): a 401 from OpenAI's OAuth token
|
||||
// endpoint means the refresh credential itself was rejected (e.g. rotated away
|
||||
// or a payload whose error code we do not yet recognize). Retrying with the
|
||||
// same refresh token will never succeed — surface re-auth, do not loop.
|
||||
test("refreshCodexToken treats any 401 from the token endpoint as unrecoverable", async () => {
|
||||
const log = createLog();
|
||||
|
||||
await withMockedFetch(
|
||||
async () =>
|
||||
textResponse(
|
||||
JSON.stringify({
|
||||
error: {
|
||||
// A payload variant whose code/type are NOT in the existing
|
||||
// unrecoverable set — only the 401 status proves the token is dead.
|
||||
message: "Could not validate your token. Please try signing in again.",
|
||||
type: "invalid_request_error",
|
||||
},
|
||||
}),
|
||||
401
|
||||
),
|
||||
async () => {
|
||||
const result = await refreshCodexToken("codex-refresh", log);
|
||||
assert.equal(
|
||||
result?.error,
|
||||
"unrecoverable_refresh_error",
|
||||
"401 from OpenAI token endpoint must surface re-auth instead of returning null (which triggers retry)"
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test("refreshKiroToken uses the AWS OIDC flow when client credentials are present", async () => {
|
||||
const log = createLog();
|
||||
const calls: any[] = [];
|
||||
|
||||
Reference in New Issue
Block a user