{"record":{"id":"d3324c07afdeecf4","repo":"TryGhost/Ghost","slug":"failed-to-fetch-changelog-response-status","errorCode":null,"errorMessage":"Failed to fetch changelog: ${response.status}","messagePattern":"Failed to fetch changelog: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"apps/admin/src/whats-new/hooks/use-changelog.ts","lineNumber":51,"sourceCode":"        posts: z.array(ChangelogEntrySchema).default([]),\n        changelogUrl: z.string().url().default(\"https://ghost.org/changelog\"),\n    })\n    .transform((data) => ({\n        entries: data.posts,\n        changelogUrl: data.changelogUrl,\n    }));\n\nexport type RawChangelogResponse = z.input<typeof ChangelogResponseSchema>;\nexport type ChangelogResponse = z.output<typeof ChangelogResponseSchema>;\n\nexport const useChangelog = () =>\n    useQuery({\n        queryKey: [\"changelog\"],\n        queryFn: async () => {\n            const response = await fetch(\"https://ghost.org/changelog.json\");\n\n            if (!response.ok) {\n                throw new Error(`Failed to fetch changelog: ${response.status}`);\n            }\n\n            const data = (await response.json()) as unknown;\n\n            return ChangelogResponseSchema.parse(data);\n        },\n        staleTime: Infinity,\n    });\n","sourceCodeStart":33,"sourceCodeEnd":60,"githubUrl":"https://github.com/TryGhost/Ghost/blob/47d8b0e2ad2fd4757d3bc45f46c3ac165ff8a1fe/apps/admin/src/whats-new/hooks/use-changelog.ts#L33-L60","documentation":"useChangelog is a TanStack Query useQuery hook that fetches https://ghost.org/changelog.json to populate the in-admin 'What's New' panel. If the fetch resolves but response.ok is false (HTTP 4xx/5xx), it throws with the status code. The error surfaces as the query's error state and triggers React Query's retry/errorBoundary behavior.","triggerScenarios":"The fetch to ghost.org/changelog.json returns a non-2xx status. Common causes: a 404 if the endpoint path changes, a 403 if Cloudflare blocks the request (geo or bot rules), a 5xx during a Ghost infra outage, or a captive-portal/proxy returning a 200-shaped error page that still has ok=false. Network failures (fetch rejects) produce a different error (TypeError) before this line.","commonSituations":"Corporate proxy or firewall blocks or rewrites the request. Ghost infra is temporarily down. The admin is loaded in an environment without internet (air-gapped, local-only dev). A browser extension intercepts the request. DNS resolution returns a captive portal page.","solutions":["Check browser DevTools Network tab for the actual status and response body of the changelog.json request.","If behind a proxy, ensure ghost.org is allowlisted or disable the changelog panel in offline environments.","React Query retries by default (3 attempts) — if transient, the error clears on retry; for permanent blocks, suppress the panel via the feature flag or conditional rendering on query error.","If the endpoint moved, update the URL in use-changelog.ts:48."],"exampleFix":null,"handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"// useChangelog uses React Query; consume the error state in the component:\nconst {data, error, isLoading} = useChangelog();\nif (error) {\n  // render the panel in a degraded/hidden state\n  return null;\n}","preventionTips":["Gate the 'What's New' panel behind the query's success state so a fetch failure does not break the admin.","Ensure network egress to ghost.org is allowed in air-gapped/proxied environments or disable the panel there.","Rely on React Query's default retry (3 attempts) for transient failures; do not add manual retries that double the load."],"tags":["network","fetch","react-query","changelog","whats-new"],"backgroundTag":null,"analyzedSha":"47d8b0e2ad2fd4757d3bc45f46c3ac165ff8a1fe","analyzedAt":"2026-08-13T01:25:26.651Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}