{"record":{"id":"89da3fe9be7a4317","repo":"Crosstalk-Solutions/project-nomad","slug":"failed-to-fetch-update-status","errorCode":null,"errorMessage":"Failed to fetch update status","messagePattern":"Failed to fetch update status","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"admin/inertia/pages/settings/update.tsx","lineNumber":76,"sourceCode":"  // window across the admin container restart. If we resurface to 'idle'\n  // after seeing an advanced stage, treat it as the missed completion.\n  const seenAdvancedStageRef = useRef(false)\n\n  const earlyAccessSetting = useSystemSetting({\n    key: 'system.earlyAccess', initialData: {\n      key: 'system.earlyAccess',\n      value: props.system.earlyAccess,\n    }\n  })\n\n  useEffect(() => {\n    if (!isUpdating) return\n\n    const interval = setInterval(async () => {\n      try {\n        const response = await api.getSystemUpdateStatus()\n        if (!response) {\n          throw new Error('Failed to fetch update status')\n        }\n        setUpdateStatus(response)\n\n        if (ADVANCED_STAGES.has(response.stage)) {\n          seenAdvancedStageRef.current = true\n        }\n\n        // If we can connect again, hide the connection lost notice\n        setShowConnectionLostNotice(false)\n\n        // Check if update is complete or errored. We also treat a return to\n        // 'idle' as completion if we previously saw an advanced stage — this\n        // catches the race where the sidecar's brief 'complete' window passes\n        // while we're disconnected during the admin container restart.\n        const isComplete =\n          response.stage === 'complete' ||\n          (response.stage === 'idle' && seenAdvancedStageRef.current)\n","sourceCodeStart":58,"sourceCodeEnd":94,"githubUrl":"https://github.com/Crosstalk-Solutions/project-nomad/blob/0bd1c6f4f9888d577fe232de06ac144bb8337131/admin/inertia/pages/settings/update.tsx#L58-L94","documentation":"Thrown by the update page's polling loop (setInterval) when api.getSystemUpdateStatus() resolves to a falsy value. It signals the update-status endpoint returned an empty/unparseable body while the UI is in isUpdating mode and polling progress.","triggerScenarios":"Polling GET /system-update/status during an active update where the request is aborted, returns 204/empty body, an interceptor swallows the body, or the server briefly 502s behind a proxy.","commonSituations":"Reverse proxy (nginx/traefik) timing out long update requests, the backend restarting mid-update so status is briefly unavailable, or an axios response interceptor returning undefined on error paths.","solutions":["Inspect the network tab for the failing status request's HTTP status/body","Make the axios wrapper return response.data and not swallow errors — an empty 200 should be surfaced as HTTP detail","Treat transient poll failures leniently: tolerate N consecutive failures before erroring the UI","If the backend restarts during update, persist update stage server-side so status recovers"],"exampleFix":"// before\nconst response = await api.getSystemUpdateStatus()\nif (!response) {\n  throw new Error('Failed to fetch update status')\n}\n// after\nconst response = await api.getSystemUpdateStatus().catch(() => null)\nif (!response) {\n  pollFailuresRef.current += 1\n  if (pollFailuresRef.current >= 3) throw new Error('Failed to fetch update status')\n  return\n}\npollFailuresRef.current = 0","handlingStrategy":"retry","validationCode":"if (!isUpdating) return // don't poll when not updating","typeGuard":null,"tryCatchPattern":"try {\n  const response = await api.getSystemUpdateStatus()\n  if (!response) throw new Error('Failed to fetch update status')\n  failuresRef.current = 0\n} catch {\n  failuresRef.current += 1\n  if (failuresRef.current >= 3) setError('Lost contact with update status')\n}","preventionTips":["Use React Query's useInterval polling with built-in retry instead of raw setInterval","Tolerate transient poll failures before surfacing an error","Clear the interval reliably on unmount to avoid stale polls"],"tags":["polling","system-update","frontend"],"backgroundTag":"polling-empty-response","analyzedSha":"0bd1c6f4f9888d577fe232de06ac144bb8337131","analyzedAt":"2026-08-27T05:34:15.424Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}