supabase/supabase · error · ResponseError

Failed to fetch incident status: ${response.statusText}

Error message

Failed to fetch incident status: ${response.statusText}

What it means

Thrown by getIncidentStatus as a ResponseError when the fetch to the incident-status endpoint returns non-2xx. Unlike the banner error, this carries structured data: response.status and an optional retryAfter parsed from the Retry-After response header (only when it is a finite positive number). The function also logs the status and body text to console.error before throwing.

Source

Thrown at apps/studio/data/platform/incident-status-query.ts:32

    method: 'GET',
    credentials: 'omit',
    headers: {
      'Content-Type': 'application/json',
    },
  })

  if (!response.ok) {
    const errorText = await response.text()
    console.error('[getIncidentStatus] Failed:', response.status, errorText)

    let retryAfter: number | undefined
    const retryAfterHeader = response.headers.get('Retry-After')
    if (retryAfterHeader !== null) {
      const parsed = Number(retryAfterHeader)
      if (Number.isFinite(parsed) && parsed > 0) retryAfter = parsed
    }

    throw new ResponseError(
      `Failed to fetch incident status: ${response.statusText}`,
      response.status,
      undefined,
      retryAfter
    )
  }

  const data = await response.json()
  const [maintenanceEvents, incidents] = partition(
    data ?? [],
    (event) => event.impact === 'maintenance'
  )
  return { maintenanceEvents, incidents }
}

export type IncidentStatusData = Awaited<ReturnType<typeof getIncidentStatus>>
export type IncidentStatusError = unknown

View on GitHub (pinned to beee91b9c2)

Solutions

  1. Inspect err.status and honor err.retryAfter before retrying the query.
  2. Surface a degraded status state in the UI rather than crashing.
  3. Check the upstream status provider and Studio API health.
  4. For 429s, configure the query's retryDelay to respect the Retry-After value.

Example fix

// before: generic catch
try { await getIncidentStatus() } catch (e) { /* ? */ }
// after: structured handling
try {
  const status = await getIncidentStatus()
} catch (e) {
  if (e instanceof ResponseError && e.status === 429 && e.retryAfter) {
    scheduleRetryIn(e.retryAfter)
  } else {
    showDegradedStatus()
  }
}
Defensive patterns

Strategy: try-catch

Type guard

const isIncidentStatusResponseError = (e: unknown): e is ResponseError =>
  e instanceof ResponseError && /Failed to fetch incident status/.test(e.message)

// 429 with Retry-After
const isRateLimited = (e: ResponseError): boolean =>
  e.status === 429 && typeof e.retryAfter === 'number' && e.retryAfter > 0

Try / catch

try {
  const status = await getIncidentStatus(signal)
  // render status
} catch (e) {
  if (e instanceof ResponseError) {
    if (e.status === 429 && typeof e.retryAfter === 'number') {
      // honor Retry-After before next query attempt
      await delay(e.retryAfter * 1000)
    } else if (e.status >= 500) {
      showDegradedStatus()
    } else {
      // 4xx other than 429: surface to UI
      showStatusError(e)
    }
    return
  }
  throw e
}

Prevention

When it happens

Trigger: The incident-status endpoint returns 4xx/5xx. A 429 typically carries a Retry-After header that the function captures and exposes on the error; a 5xx indicates the upstream status provider or Studio API is failing.

Common situations: Status provider rate-limited (429 + Retry-After); upstream status feed down; Studio API route misconfigured; auth/permission issues surfacing as 401/403.

Related errors


AI-assisted analysis of supabase/supabase@beee91b9c2 (2026-08-12). Data as JSON: /api/errors/0399df2e2b169547. Report an issue: GitHub.