{"record":{"id":"d0718eb58945fa8c","repo":"QuantumNous/new-api","slug":"failed-to-fetch-usage-d0718e","errorCode":null,"errorMessage":"Failed to fetch usage","messagePattern":"Failed to fetch usage","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web/src/features/channels/components/dialogs/balance-query-dialog.tsx","lineNumber":68,"sourceCode":"  const queryClient = useQueryClient()\n  const [isQuerying, setIsQuerying] = useState(false)\n  const [balance, setBalance] = useState<number | null>(null)\n  const [balanceUpdatedTime, setBalanceUpdatedTime] = useState<number | null>(\n    null\n  )\n  const [codexUsageResponse, setCodexUsageResponse] =\n    useState<CodexUsageDialogData | null>(null)\n\n  const isCodex = currentRow?.type === 57\n\n  const handleQueryCodexUsage = async () => {\n    const row = currentRow\n    if (!row) return\n    setIsQuerying(true)\n    try {\n      const res = await getCodexUsage(row.id)\n      if (!res.success) {\n        throw new Error(res.message || t('Failed to fetch usage'))\n      }\n      setCodexUsageResponse(res)\n    } catch (error: unknown) {\n      toast.error(\n        error instanceof Error ? error.message : t('Failed to fetch usage')\n      )\n    } finally {\n      setIsQuerying(false)\n    }\n  }\n\n  useEffect(() => {\n    if (!isCodex) return\n    if (!open) return\n    handleQueryCodexUsage()\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [open, isCodex])\n","sourceCodeStart":50,"sourceCodeEnd":86,"githubUrl":"https://github.com/QuantumNous/new-api/blob/e2c7aa7b102c2075eae2377df3508658d45e88dc/web/src/features/channels/components/dialogs/balance-query-dialog.tsx#L50-L86","documentation":"Thrown by handleQueryCodexUsage in the balance-query dialog when the Codex usage lookup fails. It calls getCodexUsage(row.id); if the response has success=false or the fetch rejects, the backend message (or this i18n fallback) is toasted. The dialog only shows this action for channels with type === 57.","triggerScenarios":"Opening Balance Query on a type-57 (Codex) channel and triggering the query while getCodexUsage returns success=false or throws — invalid credential, upstream Codex API error, deleted channel id, or expired admin session.","commonSituations":"Codex channel credential expired or revoked upstream; the channel was deleted in another tab so row.id no longer exists; backend has no route/permission for the requesting user role; network interruption between browser and gateway.","solutions":["Verify the channel still exists and its key is current (re-save the key, then retry the dialog)","Call the codex usage API for row.id directly and inspect the JSON message — it is shown instead of the fallback when present","Check backend logs for the proxied upstream request failing (401/403/timeout from Codex)","Confirm your admin session is still valid (refresh the page; re-login if 401)"],"exampleFix":"// before\nconst res = await getCodexUsage(row.id)\nif (!res.success) {\n  throw new Error(res.message || t('Failed to fetch usage'))\n}\n// after - distinguish empty data from failure\nconst res = await getCodexUsage(row.id)\nif (!res.success) {\n  throw new Error(res.message || t('Failed to fetch usage'))\n}\nif (!res.data) {\n  toast.warning(t('No usage data returned for this channel'))\n  return\n}","handlingStrategy":"try-catch","validationCode":"if (!row || row.type !== 57) return // dialog action is Codex-only","typeGuard":"const isCodexRow = (row: ChannelRow | null | undefined): row is ChannelRow =>\n  !!row && row.type === 57 && typeof row.id === 'number'","tryCatchPattern":"try {\n  const res = await getCodexUsage(row.id)\n  if (!res.success) throw new Error(res.message || t('Failed to fetch usage'))\n  setCodexUsageResponse(res)\n} catch (error: unknown) {\n  toast.error(error instanceof Error ? error.message : t('Failed to fetch usage'))\n} finally {\n  setIsQuerying(false)\n}","preventionTips":["Render the Codex query action only when isCodex (type === 57) is true","Invalidate cached responses on dialog open so stale failures do not persist","Log the failing channel id alongside the error for triage"],"tags":["api-error","codex","balance-query","dialog","i18n"],"backgroundTag":null,"analyzedSha":"e2c7aa7b102c2075eae2377df3508658d45e88dc","analyzedAt":"2026-08-15T10:35:18.111Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}