Crosstalk-Solutions/project-nomad · warning

Failed to fetch update status

Error message

Failed to fetch update status

What it means

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.

Source

Thrown at admin/inertia/pages/settings/update.tsx:76

  // window across the admin container restart. If we resurface to 'idle'
  // after seeing an advanced stage, treat it as the missed completion.
  const seenAdvancedStageRef = useRef(false)

  const earlyAccessSetting = useSystemSetting({
    key: 'system.earlyAccess', initialData: {
      key: 'system.earlyAccess',
      value: props.system.earlyAccess,
    }
  })

  useEffect(() => {
    if (!isUpdating) return

    const interval = setInterval(async () => {
      try {
        const response = await api.getSystemUpdateStatus()
        if (!response) {
          throw new Error('Failed to fetch update status')
        }
        setUpdateStatus(response)

        if (ADVANCED_STAGES.has(response.stage)) {
          seenAdvancedStageRef.current = true
        }

        // If we can connect again, hide the connection lost notice
        setShowConnectionLostNotice(false)

        // Check if update is complete or errored. We also treat a return to
        // 'idle' as completion if we previously saw an advanced stage — this
        // catches the race where the sidecar's brief 'complete' window passes
        // while we're disconnected during the admin container restart.
        const isComplete =
          response.stage === 'complete' ||
          (response.stage === 'idle' && seenAdvancedStageRef.current)

View on GitHub (pinned to 0bd1c6f4f9)

Solutions

  1. Inspect the network tab for the failing status request's HTTP status/body
  2. Make the axios wrapper return response.data and not swallow errors — an empty 200 should be surfaced as HTTP detail
  3. Treat transient poll failures leniently: tolerate N consecutive failures before erroring the UI
  4. If the backend restarts during update, persist update stage server-side so status recovers

Example fix

// before
const response = await api.getSystemUpdateStatus()
if (!response) {
  throw new Error('Failed to fetch update status')
}
// after
const response = await api.getSystemUpdateStatus().catch(() => null)
if (!response) {
  pollFailuresRef.current += 1
  if (pollFailuresRef.current >= 3) throw new Error('Failed to fetch update status')
  return
}
pollFailuresRef.current = 0
Defensive patterns

Strategy: retry

Validate before calling

if (!isUpdating) return // don't poll when not updating

Try / catch

try {
  const response = await api.getSystemUpdateStatus()
  if (!response) throw new Error('Failed to fetch update status')
  failuresRef.current = 0
} catch {
  failuresRef.current += 1
  if (failuresRef.current >= 3) setError('Lost contact with update status')
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of Crosstalk-Solutions/project-nomad@0bd1c6f4f9 (2026-08-27). Data as JSON: /api/errors/89da3fe9be7a4317. Report an issue: GitHub.