From 65ea6a0d9482a36e7e9bc1227e57637e6cbb1924 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sat, 25 Apr 2026 02:31:42 -0700 Subject: [PATCH] fix(auth): clarify Codex OAuth region failures (#71501) --- CHANGELOG.md | 1 + .../provider-openai-codex-oauth.test.ts | 22 +++++++++++++++++++ src/plugins/provider-openai-codex-oauth.ts | 15 ++++++++++++- 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4cc7be2326..60a3f217185 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ Docs: https://docs.openclaw.ai - Google Chat: preserve reply text when a typing indicator message is deleted or can no longer be updated, so media captions and first text chunks are resent instead of silently disappearing. (#71498) Thanks @colin-lgtm. - Heartbeat: clamp oversized scheduler delays through the shared safe timer helper, preventing `every` values over Node's timeout cap from becoming a 1 ms crash loop. Fixes #71414. (#71478) Thanks @hclsys. - Control UI/chat: collapse assistant token/model context details behind an explicit Context disclosure and show full dates in message footers, making historical transcript timing clear without noisy default metadata. (#71337) Thanks @BunsDev. +- OpenAI/Codex OAuth: explain `unsupported_country_region_territory` token-exchange failures with a proxy/region hint instead of surfacing a generic OAuth error. Fixes #51175. - Telegram: remove the startup persisted-offset `getUpdates` preflight so polling restarts do not self-conflict before the runner starts. Fixes #69304. (#69779) Thanks @chinar-amrutkar. - Telegram: keep the polling stall watchdog active even when grammY reports the runner as not running while its task is still pending, so a rebuilt transport cannot leave `getUpdates` silent until a manual gateway restart. Fixes #69064. Thanks @LDLoeb. - Browser/Playwright: ignore benign already-handled route races during guarded navigation so browser-page tasks no longer fail when Playwright tears down a route mid-flight. (#68708) Thanks @Steady-ai. diff --git a/src/plugins/provider-openai-codex-oauth.test.ts b/src/plugins/provider-openai-codex-oauth.test.ts index d1c6bcef55e..427e0a21624 100644 --- a/src/plugins/provider-openai-codex-oauth.test.ts +++ b/src/plugins/provider-openai-codex-oauth.test.ts @@ -181,6 +181,28 @@ describe("loginOpenAICodexOAuth", () => { ); }); + it("explains OpenAI unsupported region token exchange failures", async () => { + mocks.loginOpenAICodex.mockRejectedValue(new Error("403 unsupported_country_region_territory")); + + const { prompter, spin } = createPrompter(); + const runtime = createRuntime(); + await expect( + loginOpenAICodexOAuth({ + prompter, + runtime, + isRemote: false, + openUrl: async () => {}, + }), + ).rejects.toThrow(/unsupported_region/i); + + expect(spin.stop).toHaveBeenCalledWith("OpenAI OAuth failed"); + expect(runtime.error).toHaveBeenCalledWith(expect.stringContaining("HTTPS_PROXY")); + expect(prompter.note).toHaveBeenCalledWith( + "Trouble with OAuth? See https://docs.openclaw.ai/start/faq", + "OAuth help", + ); + }); + it("passes manual code input hook for remote oauth flows", async () => { const creds = createCodexCredentials(); mocks.loginOpenAICodex.mockImplementation(async (opts: CodexLoginOptions) => { diff --git a/src/plugins/provider-openai-codex-oauth.ts b/src/plugins/provider-openai-codex-oauth.ts index c94c9a990e5..fc41bf37d22 100644 --- a/src/plugins/provider-openai-codex-oauth.ts +++ b/src/plugins/provider-openai-codex-oauth.ts @@ -15,7 +15,10 @@ const openAICodexOAuthOriginator = "openclaw"; const localManualFallbackDelayMs = 15_000; const localManualFallbackGraceMs = 1_000; -type OpenAICodexOAuthFailureCode = "callback_timeout" | "callback_validation_failed"; +type OpenAICodexOAuthFailureCode = + | "callback_timeout" + | "callback_validation_failed" + | "unsupported_region"; function waitForDelayOrLoginSettle(params: { delayMs: number; @@ -54,6 +57,16 @@ function createOpenAICodexOAuthError( function rewriteOpenAICodexOAuthError(error: unknown): Error { const message = formatErrorMessage(error); + if (/unsupported_country_region_territory/i.test(message)) { + return createOpenAICodexOAuthError( + "unsupported_region", + [ + "OpenAI rejected the token exchange for this country, region, or network route.", + "If you normally use a proxy, verify HTTPS_PROXY, HTTP_PROXY, or ALL_PROXY is set for the OpenClaw process and then retry `openclaw models auth login --provider openai-codex`.", + ].join(" "), + error, + ); + } if (/state mismatch|missing authorization code/i.test(message)) { return createOpenAICodexOAuthError("callback_validation_failed", message, error); }