{"record":{"id":"ef7a3799ee8edf3c","repo":"actualbudget/actual","slug":"failed-to-fetch-catalog-response-statustext","errorCode":null,"errorMessage":"Failed to fetch catalog: ${response.statusText}","messagePattern":"Failed to fetch catalog: (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"packages/desktop-client/src/hooks/useThemeCatalog.ts","lineNumber":24,"sourceCode":"\n/**\n * Custom hook to fetch and manage the theme catalog from GitHub.\n */\nexport function useThemeCatalog() {\n  const [data, setData] = useState<CatalogTheme[] | null>(null);\n  const [isLoading, setIsLoading] = useState(true);\n  const [error, setError] = useState<string | null>(null);\n\n  useEffect(() => {\n    const fetchCatalog = async () => {\n      setIsLoading(true);\n      setError(null);\n\n      try {\n        const response = await fetch(CATALOG_URL);\n\n        if (!response.ok) {\n          throw new Error(`Failed to fetch catalog: ${response.statusText}`);\n        }\n\n        const data = await response.json();\n\n        // Validate that data is an array\n        if (!Array.isArray(data)) {\n          throw new Error('Invalid catalog format: expected an array');\n        }\n\n        setData(data);\n      } catch (err) {\n        setError(\n          err instanceof Error ? err.message : 'Failed to load theme catalog',\n        );\n      } finally {\n        setIsLoading(false);\n      }\n    };","sourceCodeStart":6,"sourceCodeEnd":42,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/desktop-client/src/hooks/useThemeCatalog.ts#L6-L42","documentation":"useThemeCatalog's fetchCatalog fetches the theme catalog from CATALOG_URL and throws 'Failed to fetch catalog: <statusText>' when response.ok is false (non-2xx HTTP status). The statusText is embedded so the developer can see why the request failed (e.g. Not Found, Service Unavailable). The error is captured and stored via setError rather than thrown to the caller.","triggerScenarios":"The catalog URL returns 404 (wrong/removed URL), 403 (blocked), 5xx (server error), or is redirected to an error page while useThemeCatalog mounts and fetchCatalog runs.","commonSituations":"Offline development environments or corporate proxies blocking the request; the catalog host being down; CATALOG_URL pointing to a stale domain; rate limiting returning 429.","solutions":["Check CATALOG_URL in useThemeCatalog.ts and confirm it resolves correctly (open it in a browser or with curl -I)","Retry later or add retry/backoff logic — the error often indicates a transient server or network problem","Serve the catalog locally or bundle a fallback static catalog when offline","Inspect response.status (not just statusText) for richer diagnostics in the error message"],"exampleFix":"// before\nif (!response.ok) {\n  throw new Error(`Failed to fetch catalog: ${response.statusText}`);\n}\n// after\nif (!response.ok) {\n  throw new Error(`Failed to fetch catalog: ${response.status} ${response.statusText} (${CATALOG_URL})`);\n}","handlingStrategy":"retry","validationCode":"// Before fetching, check connectivity when available:\nif (typeof navigator !== 'undefined' && navigator.onLine === false) {\n  console.warn('Offline: theme catalog will not load');\n}","typeGuard":"const isHttpOk = (r: Response): r is Response & { ok: true } => r.ok;","tryCatchPattern":"const { catalog, error } = useThemeCatalog();\nif (error?.startsWith('Failed to fetch catalog')) {\n  render(<FallbackThemeList />); // or a Retry button re-running fetchCatalog\n}","preventionTips":["Add retry with exponential backoff around the catalog fetch","Pin CATALOG_URL via configuration so it can be corrected without a rebuild","Check the endpoint's uptime and status codes with curl -I during development","Bundle a minimal fallback catalog for offline use"],"tags":["network","fetch","http"],"backgroundTag":"http-request-failed","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}