{"record":{"id":"6016c49444cadb94","repo":"agalwood/Motrix","slug":"manifest-fetch-failed-http-res-status","errorCode":null,"errorMessage":"manifest fetch failed: HTTP ${res.status}","messagePattern":"manifest fetch failed: HTTP (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/core/media/manifest-fetcher.ts","lineNumber":18,"sourceCode":"const DEFAULT_MAX = 5 * 1024 * 1024\n\nexport async function fetchManifest(\n  url: string,\n  opts: {\n    headers?: Record<string, string>\n    fetchImpl?: typeof fetch\n    maxBytes?: number\n  } = {}\n): Promise<string> {\n  const doFetch = opts.fetchImpl ?? fetch\n  const res = await doFetch(url, {\n    method: 'GET',\n    headers: opts.headers ?? {},\n    redirect: 'follow',\n  })\n  if (!res.ok) {\n    throw new Error(`manifest fetch failed: HTTP ${res.status}`)\n  }\n  const text = await res.text()\n  const max = opts.maxBytes ?? DEFAULT_MAX\n  if (text.length > max) {\n    throw new Error(`manifest too large: ${text.length} > ${max}`)\n  }\n  return text\n}\n","sourceCodeStart":1,"sourceCodeEnd":27,"githubUrl":"https://github.com/agalwood/Motrix/blob/1a708ee57746c434e2c67a44bbf0906a976afea4/src/core/media/manifest-fetcher.ts#L1-L27","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","Pass opts.headers with the required Authorization/Referer/User-Agent/Cookie for the CDN.","For pre-signed URLs, regenerate them closer to fetch time (they typically expire in minutes–hours).","Wrap fetchManifest in a retry-with-backoff for 429/5xx, capped at a few attempts."],"exampleFix":"// before\nconst text = await fetchManifest(url, {})\n// after — pass auth headers and retry on transient failures\nasync function fetchWithRetry(url, headers, attempts = 3) {\n  for (let i = 0; i < attempts; i++) {\n    try {\n      return await fetchManifest(url, { headers })\n    } catch (e) {\n      if (i === attempts - 1 || !/HTTP (429|5\\d\\d)/.test(String(e))) throw e\n      await new Promise(r => setTimeout(r, 500 * 2 ** i))\n    }\n  }\n}","handlingStrategy":"try-catch","validationCode":"// Validate URL reachability before fetchManifest if you want an early signal.\nfunction looksLikeManifestUrl(url: string): boolean {\n  return /\\.(m3u8|mpd|json)(\\?|$)/i.test(url)\n}\nif (!looksLikeManifestUrl(url)) {\n  throw new Error(`URL does not look like a manifest: ${url}`)\n}","typeGuard":null,"tryCatchPattern":"try {\n  const text = await fetchManifest(url, { headers })\n} catch (e) {\n  const status = /HTTP (\\d{3})/.exec(String(e))?.[1]\n  if (status === '404') { /* refresh URL */ }\n  else if (status === '401' || status === '403') { /* fix auth */ }\n  else if (status === '429' || /^5\\d\\d$/.test(status ?? '')) { /* back off and retry */ }\n  else throw e\n}","preventionTips":["Pass required headers (Authorization, Referer, User-Agent, Cookie) via opts.headers.","Refresh pre-signed URLs immediately before fetching them.","Wrap fetchManifest in a bounded retry with exponential backoff for 429/5xx."],"tags":["network","http","media","streaming"],"backgroundTag":null,"analyzedSha":"1a708ee57746c434e2c67a44bbf0906a976afea4","analyzedAt":"2026-08-12T16:18:09.346Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}