{"record":{"id":"12e4bcb884fda298","repo":"decolua/9router","slug":"failed-to-load-codex-reset-credits","errorCode":null,"errorMessage":"Failed to load Codex reset credits","messagePattern":"Failed to load Codex reset credits","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.js","lineNumber":337,"sourceCode":"\n        await fetchQuota(connectionId, provider);\n        setLastUpdated(new Date());\n      } catch (error) {\n        setErrors((prev) => ({ ...prev, [connectionId]: error.message || \"Failed to reset Codex limit\" }));\n      } finally {\n        setResettingLimitId(null);\n      }\n    },\n    [fetchQuota, resettingLimitId],\n  );\n\n  const handleViewCodexResetCredits = useCallback(async (connection) => {\n    setResetCreditsState({ connection, loading: true, error: null, data: null });\n    try {\n      const response = await fetch(`/api/usage/${connection.id}/codex-reset-credits`, { cache: \"no-store\" });\n      const result = await response.json().catch(() => ({}));\n      if (!response.ok) {\n        throw new Error(result.error || result.message || \"Failed to load Codex reset credits\");\n      }\n      const credits = Array.isArray(result.credits) ? [...result.credits] : [];\n      credits.sort((a, b) => {\n        const aTime = a.expiresAt ? new Date(a.expiresAt).getTime() : Number.POSITIVE_INFINITY;\n        const bTime = b.expiresAt ? new Date(b.expiresAt).getTime() : Number.POSITIVE_INFINITY;\n        return aTime - bTime;\n      });\n      setResetCreditsState({ connection, loading: false, error: null, data: { ...result, credits } });\n    } catch (error) {\n      setResetCreditsState({ connection, loading: false, error: error.message || \"Failed to load Codex reset credits\", data: null });\n    }\n  }, []);\n\n  const handleDeleteConnection = useCallback(\n    async (id) => {\n      if (!confirm(\"Delete this connection?\")) return;\n      setDeletingId(id);\n      try {","sourceCodeStart":319,"sourceCodeEnd":355,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.js#L319-L355","documentation":"handleViewCodexResetCredits GETs /api/usage/<connectionId>/codex-reset-credits (cache: no-store) to list the connection's available reset credits and throws result.error || result.message || 'Failed to load Codex reset credits' on non-OK. The route 404s for unknown connections, 400s for non-Codex or non-OAuth/access-token connections, 401s when the OAuth credential refresh fails, and 500s when the upstream credits lookup throws.","triggerScenarios":"GET /api/usage/<id>/codex-reset-credits returns non-OK while opening the reset-credits modal: 404 'Connection not found', 400 'Codex reset credits are only available for Codex connections' / 'require an OAuth or access-token connection', 401 'Credential refresh failed: ...', or 500 from the upstream getCodexRateLimitResetCredits call.","commonSituations":"Opening the modal for a stale/deleted connection; connection authenticated via API key only; Codex OAuth token expired and refresh token invalid; network/proxy failure reaching the Codex usage endpoint.","solutions":["Verify the connection still exists — reload the connections list (a 404 means it was deleted).","Confirm the connection is provider=codex with authType oauth or access_token; otherwise the endpoint intentionally returns 400.","On 'Credential refresh failed', re-authorize the Codex OAuth connection in the dashboard.","Retry after checking network/proxy settings if the 500 stems from the upstream credits lookup."],"exampleFix":"// before\nthrow new Error(result.error || result.message || \"Failed to load Codex reset credits\");\n// after\nthrow new Error(result.error || result.message || `Failed to load Codex reset credits (HTTP ${response.status})`);","handlingStrategy":"validation","validationCode":"if (!connection || connection.provider !== \"codex\") { setResetCreditsState({ connection: null, loading: false, error: \"Not a Codex connection\", data: null }); return; }\nif (![\"oauth\", \"access_token\"].includes(connection.authType)) { setResetCreditsState({ connection: null, loading: false, error: \"OAuth or access-token connection required\", data: null }); return; }","typeGuard":"const isCreditsPayload = (r) => r && typeof r === \"object\" && (r.credits === undefined || Array.isArray(r.credits));","tryCatchPattern":"try {\n  const response = await fetch(`/api/usage/${connection.id}/codex-reset-credits`, { cache: \"no-store\" });\n  const result = await response.json().catch(() => ({}));\n  if (!response.ok) throw new Error(result.error || result.message || `Failed to load credits (HTTP ${response.status})`);\n  setResetCreditsState({ connection, loading: false, error: null, data: result.credits || [] });\n} catch (e) {\n  setResetCreditsState({ connection, loading: false, error: e.message, data: null });\n}","preventionTips":["Only open the credits modal for live, codex, oauth/access-token connections.","Handle 404 by refreshing the connections list (stale row after deletion).","Re-authorize the connection when credential-refresh failures occur."],"tags":["quota","oauth","http-error"],"backgroundTag":"http-4xx-client-error","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}