From e4458b82228cd4563194654db376ca6a729cfcb4 Mon Sep 17 00:00:00 2001 From: Stefan de Vogelaere Date: Tue, 13 Jan 2026 00:47:51 +0100 Subject: [PATCH] fix(codex): prevent infinite loop when fetching models on settings screen When Codex is not connected/authenticated, the /api/codex/models endpoint returns 503. The fetchCodexModels function had no cooldown after failures, causing infinite retries when navigating to the Settings screen. Added codexModelsLastFailedAt state to track failed fetch attempts and skip retries for 30 seconds after a failure. This prevents the infinite loop while still allowing periodic retry attempts. Co-Authored-By: Claude Opus 4.5 --- apps/ui/src/store/app-store.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/apps/ui/src/store/app-store.ts b/apps/ui/src/store/app-store.ts index ca415ed6..85d7b782 100644 --- a/apps/ui/src/store/app-store.ts +++ b/apps/ui/src/store/app-store.ts @@ -689,6 +689,7 @@ export interface AppState { codexModelsLoading: boolean; codexModelsError: string | null; codexModelsLastFetched: number | null; + codexModelsLastFailedAt: number | null; // Pipeline Configuration (per-project, keyed by project path) pipelineConfigByProject: Record; @@ -1286,6 +1287,7 @@ const initialState: AppState = { codexModelsLoading: false, codexModelsError: null, codexModelsLastFetched: null, + codexModelsLastFailedAt: null, pipelineConfigByProject: {}, worktreePanelVisibleByProject: {}, showInitScriptIndicatorByProject: {}, @@ -3113,11 +3115,21 @@ export const useAppStore = create()((set, get) => ({ // Codex Models actions fetchCodexModels: async (forceRefresh = false) => { - const { codexModelsLastFetched, codexModelsLoading } = get(); + const { codexModelsLastFetched, codexModelsLoading, codexModelsLastFailedAt } = get(); // Skip if already loading if (codexModelsLoading) return; + // Skip if recently failed (< 30 seconds ago) and not forcing refresh + const FAILURE_COOLDOWN = 30000; // 30 seconds + if ( + !forceRefresh && + codexModelsLastFailedAt && + Date.now() - codexModelsLastFailedAt < FAILURE_COOLDOWN + ) { + return; + } + // Skip if recently fetched (< 5 minutes ago) and not forcing refresh if (!forceRefresh && codexModelsLastFetched && Date.now() - codexModelsLastFetched < 300000) { return; @@ -3142,12 +3154,14 @@ export const useAppStore = create()((set, get) => ({ codexModelsLastFetched: Date.now(), codexModelsLoading: false, codexModelsError: null, + codexModelsLastFailedAt: null, // Clear failure on success }); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; set({ codexModelsError: errorMessage, codexModelsLoading: false, + codexModelsLastFailedAt: Date.now(), // Record failure time for cooldown }); } },