From 837925c20588cc42eb8589cecf1b40596a18cfa9 Mon Sep 17 00:00:00 2001 From: Js2Hou Date: Sat, 27 Jun 2026 22:41:23 +0800 Subject: [PATCH 1/2] fix: prevent Cloudflare challenge from blocking usage refresh When codex is running, the accounts/check endpoint (chatgpt.com/backend-api/accounts/check/v4-2023-04-27) may be intercepted by Cloudflare's managed challenge (403), causing the entire usage refresh to fail before get_usage (wham/usage) ever executes. Changes: - Backend: detect Cloudflare challenge response and return a readable error instead of dumping the challenge HTML page - Frontend: wrap metadata refresh in try/catch so that plan-type / subscription fetch failures never block the core usage (rate-limit / credits) refresh --- src-tauri/src/api/usage.rs | 12 ++++++++++++ src/hooks/useAccounts.ts | 40 +++++++++++++++++++++++++------------- 2 files changed, 39 insertions(+), 13 deletions(-) diff --git a/src-tauri/src/api/usage.rs b/src-tauri/src/api/usage.rs index 6c4c1331..fd4212c3 100644 --- a/src-tauri/src/api/usage.rs +++ b/src-tauri/src/api/usage.rs @@ -106,6 +106,18 @@ pub async fn fetch_chatgpt_account_metadata( let status = response.status(); if !status.is_success() { let body = response.text().await.unwrap_or_default(); + + // When Cloudflare throws a managed challenge (403 + JS challenge page) the + // accounts/check endpoint becomes unavailable for non-browser clients. + // Return a readable error instead of dumping the entire HTML page. + if status.as_u16() == 403 + && (body.contains("_cf_chl_opt") || body.contains("challenge-platform")) + { + anyhow::bail!( + "Cloudflare rate-limit (403); plan info temporarily unavailable" + ); + } + anyhow::bail!("Accounts check API error: {status} - {body}"); } diff --git a/src/hooks/useAccounts.ts b/src/hooks/useAccounts.ts index 94064a8f..4cd18e8a 100644 --- a/src/hooks/useAccounts.ts +++ b/src/hooks/useAccounts.ts @@ -106,17 +106,24 @@ export function useAccounts() { } if (options?.refreshMetadata) { - await runWithConcurrency( - list, - async (account) => { - await invokeBackend("refresh_account_metadata", { - accountId: account.id, - }); - }, - maxConcurrentUsageRequests - ); - - list = await loadAccounts(true); + try { + await runWithConcurrency( + list, + async (account) => { + await invokeBackend("refresh_account_metadata", { + accountId: account.id, + }); + }, + maxConcurrentUsageRequests + ); + + list = await loadAccounts(true); + } catch (err) { + console.warn( + "Failed to refresh account metadata, continuing with usage refresh:", + err + ); + } } const accountIds = list.map((account) => account.id); @@ -178,8 +185,15 @@ export function useAccounts() { ) => { try { if (options?.refreshMetadata) { - await invokeBackend("refresh_account_metadata", { accountId }); - await loadAccounts(true); + try { + await invokeBackend("refresh_account_metadata", { accountId }); + await loadAccounts(true); + } catch (err) { + console.warn( + "Failed to refresh account metadata, continuing with usage refresh:", + err + ); + } } setAccounts((prev) => From 588e3c0f9ac4e52bd730e3bd7a8e3bd5a831ccfa Mon Sep 17 00:00:00 2001 From: Js2Hou Date: Sat, 27 Jun 2026 22:55:10 +0800 Subject: [PATCH 2/2] docs: explain portable exe and update process in README --- README.md | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f5ccc48d..aba62532 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,8 @@ Choose the file for your platform: - **macOS Apple Silicon:** `Codex.Switcher_*_aarch64.dmg` - **macOS Intel:** `Codex.Switcher_*_x64.dmg` -- **Windows:** `Codex.Switcher_*_x64-setup.exe` or `Codex.Switcher_*_x64_en-US.msi` +- **Windows (installer):** `Codex.Switcher_*_x64-setup.exe` or `Codex.Switcher_*_x64_en-US.msi` +- **Windows (portable):** `codex-switcher.exe` — single executable, no installation required - **Linux Debian/Ubuntu:** `Codex.Switcher_*_amd64.deb` - **Linux AppImage:** `Codex.Switcher_*_amd64.AppImage` - **Linux RPM:** `Codex.Switcher-*-1.x86_64.rpm` @@ -47,6 +48,28 @@ Choose the file for your platform: > open "/Applications/Codex Switcher.app" > ``` +### Windows Portable EXE + +The Windows `codex-switcher.exe` is a **self-contained executable** — no installer, no runtime, no +registry entries. Just download and run: + +1. Download `codex-switcher.exe` from the latest release +2. Place it anywhere you like (e.g. `Desktop` or `C:\Tools`) +3. Double-click to launch + +All account data (tokens, settings) is stored in `%USERPROFILE%\.codex-switcher\` and +`%USERPROFILE%\.codex\auth.json` — completely separate from the executable file. + +### Updating + +To update to a newer version: + +1. Right-click the Codex Switcher tray icon → **Quit** +2. Replace the old `codex-switcher.exe` with the new one +3. Launch the new executable + +Your accounts and settings are preserved — no re-login needed. + ### Auto Updates Codex Switcher checks the latest GitHub release on startup. When a newer signed