agalwood/Motrix · error · Error

manifest fetch failed: HTTP ${res.status}

Error message

manifest fetch failed: HTTP ${res.status}

What it means

Plain Error thrown by fetchManifest when the HTTP response status is not ok (res.ok is false). The message includes the status code so the caller can distinguish 404 (wrong URL), 401/403 (auth), 429 (rate limit), 5xx (origin failure), etc. The fetch follows redirects automatically.

Source

Thrown at src/core/media/manifest-fetcher.ts:18

const DEFAULT_MAX = 5 * 1024 * 1024

export async function fetchManifest(
  url: string,
  opts: {
    headers?: Record<string, string>
    fetchImpl?: typeof fetch
    maxBytes?: number
  } = {}
): Promise<string> {
  const doFetch = opts.fetchImpl ?? fetch
  const res = await doFetch(url, {
    method: 'GET',
    headers: opts.headers ?? {},
    redirect: 'follow',
  })
  if (!res.ok) {
    throw new Error(`manifest fetch failed: HTTP ${res.status}`)
  }
  const text = await res.text()
  const max = opts.maxBytes ?? DEFAULT_MAX
  if (text.length > max) {
    throw new Error(`manifest too large: ${text.length} > ${max}`)
  }
  return text
}

View on GitHub (pinned to 1a708ee577)

Solutions

  1. Read the status code from the message and act: 401/403 -> fix headers/auth; 404 -> refresh the URL; 429 -> back off and retry; 5xx -> retry with jitter.
  2. Pass opts.headers with the required Authorization/Referer/User-Agent/Cookie for the CDN.
  3. For pre-signed URLs, regenerate them closer to fetch time (they typically expire in minutes–hours).
  4. Wrap fetchManifest in a retry-with-backoff for 429/5xx, capped at a few attempts.

Example fix

// before
const text = await fetchManifest(url, {})
// after — pass auth headers and retry on transient failures
async function fetchWithRetry(url, headers, attempts = 3) {
  for (let i = 0; i < attempts; i++) {
    try {
      return await fetchManifest(url, { headers })
    } catch (e) {
      if (i === attempts - 1 || !/HTTP (429|5\d\d)/.test(String(e))) throw e
      await new Promise(r => setTimeout(r, 500 * 2 ** i))
    }
  }
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Validate URL reachability before fetchManifest if you want an early signal.
function looksLikeManifestUrl(url: string): boolean {
  return /\.(m3u8|mpd|json)(\?|$)/i.test(url)
}
if (!looksLikeManifestUrl(url)) {
  throw new Error(`URL does not look like a manifest: ${url}`)
}

Try / catch

try {
  const text = await fetchManifest(url, { headers })
} catch (e) {
  const status = /HTTP (\d{3})/.exec(String(e))?.[1]
  if (status === '404') { /* refresh URL */ }
  else if (status === '401' || status === '403') { /* fix auth */ }
  else if (status === '429' || /^5\d\d$/.test(status ?? '')) { /* back off and retry */ }
  else throw e
}

Prevention

When it happens

Trigger: Calling fetchManifest(url, opts) where the origin returns any non-2xx status. Commonly 401/403 when opts.headers lacks a required Authorization or Cookie; 404 for a stale/expired signed URL; 429 from a CDN rate limiter; 5xx from an origin outage.

Common situations: Expired pre-signed S3/CloudFront URLs; missing or wrong Referer/Origin/User-Agent headers required by the CDN; geo-blocks returning 451; origin maintenance windows; rate-limited scrapers.

Related errors


AI-assisted analysis of agalwood/Motrix@1a708ee577 (2026-08-12). Data as JSON: /api/errors/6016c49444cadb94. Report an issue: GitHub.