{"record":{"id":"a71dc57c29a72233","repo":"actualbudget/actual","slug":"invalid-catalog-format-expected-an-array","errorCode":null,"errorMessage":"Invalid catalog format: expected an array","messagePattern":"Invalid catalog format: expected an array","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"packages/desktop-client/src/hooks/useThemeCatalog.ts","lineNumber":31,"sourceCode":"  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    };\n\n    void fetchCatalog();\n  }, []);\n\n  return {\n    data,\n    isLoading,","sourceCodeStart":13,"sourceCodeEnd":49,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/desktop-client/src/hooks/useThemeCatalog.ts#L13-L49","documentation":"After a successful fetch, fetchCatalog validates that the parsed JSON body is an array. If the endpoint returned valid JSON of another shape (object, null, string), it throws 'Invalid catalog format: expected an array'. This protects the theme picker from runtime crashes when rendering an unexpected payload.","triggerScenarios":"CATALOG_URL serves JSON that parses but is not an array — e.g. an object like { themes: [...] }, an HTML error page parsed as text, a null response, or an API version change that wrapped the array.","commonSituations":"The catalog endpoint was updated to a paginated/envelope response format; a proxy or captive portal returned a JSON error body; misconfigured CDN serving a stub file.","solutions":["Verify the endpoint actually returns a top-level JSON array (curl the URL and inspect the body)","If the API changed, update fetchCatalog to unwrap the envelope (e.g. data.themes)","Harden the code: also validate the shape of individual catalog items before setData","Point CATALOG_URL to the correct, current catalog URL"],"exampleFix":"// before\nconst data = await response.json();\nif (!Array.isArray(data)) {\n  throw new Error('Invalid catalog format: expected an array');\n}\n// after\nconst data = await response.json();\nconst themes = Array.isArray(data) ? data : Array.isArray(data?.themes) ? data.themes : null;\nif (!themes) {\n  throw new Error('Invalid catalog format: expected an array');\n}\nsetData(themes);","handlingStrategy":"validation","validationCode":"const isThemeArray = (data: unknown): data is ThemeCatalogItem[] =>\n  Array.isArray(data) && data.every(t => !!t && typeof t === 'object' && typeof (t as any).name === 'string');","typeGuard":"const isThemeCatalog = (x: unknown): x is ThemeCatalogItem[] =>\n  Array.isArray(x) && x.every(item =>\n    typeof item === 'object' && item !== null &&\n    typeof (item as any).repo === 'string' && typeof (item as any).name === 'string'\n  );","tryCatchPattern":"const { catalog, error } = useThemeCatalog();\nif (error?.includes('Invalid catalog format')) {\n  // surface a schema-mismatch warning and use bundled themes only\n  console.error('Catalog schema changed:', error);\n}","preventionTips":["Contract-test the catalog endpoint shape in CI with a fetch + schema assertion","Keep a versioned schema (e.g. { version, themes }) and handle both shapes during migration","Never trust remote JSON: validate items before rendering","Log the received payload type when validation fails to speed up diagnosis"],"tags":["validation","json","schema"],"backgroundTag":"schema-validation-failed","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}