{"record":{"id":"31b180212632907a","repo":"QuantumNous/new-api","slug":"failed-to-refresh-credential","errorCode":null,"errorMessage":"Failed to refresh credential","messagePattern":"Failed to refresh credential","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web/src/features/channels/components/drawers/channel-mutate-drawer.tsx","lineNumber":1409,"sourceCode":"        title: t('Verify to view channel key'),\n        description: t(\n          'Use Passkey or 2FA to confirm your identity before revealing this channel key.'\n        ),\n      })\n    } catch (error) {\n      if (error instanceof Error) {\n        toast.error(error.message)\n      }\n    }\n  }, [channelId, withVerification, fetchChannelKey, t])\n\n  const handleRefreshCodexCredential = useCallback(async () => {\n    if (!channelId) return\n    setIsCodexCredentialRefreshing(true)\n    try {\n      const res = await refreshCodexCredential(channelId)\n      if (!res.success) {\n        throw new Error(res.message || t('Failed to refresh credential'))\n      }\n      toast.success(t('Credential refreshed'))\n      queryClient.invalidateQueries({\n        queryKey: channelsQueryKeys.detail(channelId),\n      })\n    } catch (error) {\n      toast.error(error instanceof Error ? error.message : t('Refresh failed'))\n    } finally {\n      setIsCodexCredentialRefreshing(false)\n    }\n  }, [channelId, queryClient, t])\n\n  // Unified function to update models\n  const updateModels = useCallback(\n    (newModels: string[], merge: boolean = false) => {\n      const finalModels = merge\n        ? formatModelsArray([...currentModelsArray, ...newModels])\n        : formatModelsArray(newModels)","sourceCodeStart":1391,"sourceCodeEnd":1427,"githubUrl":"https://github.com/QuantumNous/new-api/blob/e2c7aa7b102c2075eae2377df3508658d45e88dc/web/src/features/channels/components/drawers/channel-mutate-drawer.tsx#L1391-L1427","documentation":"Thrown by handleRefreshCodexCredential in the channel drawer when refreshCodexCredential(channelId) returns success=false. It is gated on channelId being set; the backend message or this i18n fallback is toasted, and on success the channels detail query is invalidated.","triggerScenarios":"Clicking refresh credential on a Codex channel whose token refresh fails upstream (refresh token revoked, OAuth flow incomplete, upstream 4xx), or when the request rejects (network/session).","commonSituations":"Codex/ChatGPT backend credential fully expired so refresh grant no longer works — must re-auth; upstream changed token endpoint; backend deployment lacks the refresh feature; admin session expired.","solutions":["Read the toast — res.message names the upstream reason (invalid_grant, revoked, etc.)","If the refresh token is revoked/expired, re-create the credential (re-run the auth flow) instead of refreshing","Confirm backend version supports Codex credential refresh and can reach the upstream token endpoint (proxy)","Retry after re-login if the underlying failure was a 401 session"],"exampleFix":"// before\nconst res = await refreshCodexCredential(channelId)\nif (!res.success) {\n  throw new Error(res.message || t('Failed to refresh credential'))\n}\n// after - after a successful refresh also drop cached key state\nconst res = await refreshCodexCredential(channelId)\nif (!res.success) {\n  throw new Error(res.message || t('Failed to refresh credential'))\n}\nsetChannelKey('')","handlingStrategy":"try-catch","validationCode":"if (!channelId) return // guard already present","typeGuard":null,"tryCatchPattern":"setIsCodexCredentialRefreshing(true)\ntry {\n  const res = await refreshCodexCredential(channelId)\n  if (!res.success) throw new Error(res.message || t('Failed to refresh credential'))\n  toast.success(t('Credential refreshed'))\n  queryClient.invalidateQueries({ queryKey: channelsQueryKeys.detail(channelId) })\n} catch (error) {\n  toast.error(error instanceof Error ? error.message : t('Refresh failed'))\n} finally {\n  setIsCodexCredentialRefreshing(false)\n}","preventionTips":["Disable the refresh button while isCodexCredentialRefreshing","On invalid_grant-style messages, guide re-authorization instead of retry","Invalidate the channel detail query after success so the UI shows the new credential state"],"tags":["api-error","codex","credential","oauth","drawer"],"backgroundTag":null,"analyzedSha":"e2c7aa7b102c2075eae2377df3508658d45e88dc","analyzedAt":"2026-08-15T10:35:18.111Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}