{"record":{"id":"e0b186df5b49bf1d","repo":"tinyhumansai/openhuman","slug":"manifest-fetch-failed-res-status","errorCode":null,"errorMessage":"manifest fetch failed (${res.status})","messagePattern":"manifest fetch failed \\((.+?)\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"app/src/features/human/Mascot/manifest/manifestService.ts","lineNumber":166,"sourceCode":"\n/**\n * Fetch the mascot manifest, memoised for the session. On a network failure we\n * fall back to the last localStorage snapshot so the picker still works\n * offline; if there is no snapshot either, the rejection propagates so the UI\n * can show an error state.\n */\nexport function fetchMascotManifest(): Promise<MascotManifest> {\n  if (inflight) return inflight;\n  inflight = (async () => {\n    const controller = new AbortController();\n    const timeoutId = window.setTimeout(() => controller.abort(), MANIFEST_FETCH_TIMEOUT_MS);\n    try {\n      log('fetching manifest %s', MASCOT_MANIFEST_URL);\n      const res = await fetch(MASCOT_MANIFEST_URL, {\n        cache: 'no-cache',\n        signal: controller.signal,\n      });\n      if (!res.ok) throw new Error(`manifest fetch failed (${res.status})`);\n      const manifest = parseManifest(await res.json());\n      log('manifest ok — %d mascots (schema v%d)', manifest.mascots.length, manifest.schemaVersion);\n      writeSnapshot(manifest);\n      return manifest;\n    } catch (err) {\n      const snapshot = readSnapshot();\n      if (snapshot) {\n        log('manifest fetch failed, using snapshot: %o', err);\n        return snapshot;\n      }\n      // Reset so a later retry can attempt the network again rather than\n      // re-resolving this rejected promise forever.\n      inflight = null;\n      throw err;\n    } finally {\n      window.clearTimeout(timeoutId);\n    }\n  })();","sourceCodeStart":148,"sourceCodeEnd":184,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/749120085864ce16e0f273c7b86fac7740b39c5b/app/src/features/human/Mascot/manifest/manifestService.ts#L148-L184","documentation":"fetchMascotManifest() fetches the mascot manifest over HTTP with an AbortController-backed timeout (MANIFEST_FETCH_TIMEOUT_MS). This error is thrown when the response arrives but res.ok is false — i.e. a non-2xx HTTP status such as 404 or 503. Importantly the surrounding catch then tries readSnapshot(); this Error only reaches the caller when no snapshot fallback exists.","triggerScenarios":"GET MASCOT_MANIFEST_URL returns 404 (manifest file removed/renamed upstream), 403 (rate-limited by raw CDN), 5xx (CDN or origin failure), or a captive-portal proxy returning an error page. Also triggered when the AbortController fires and fetch rejects with an AbortError — that surfaces as a different message, so this specific message is strictly an HTTP status failure.","commonSituations":"The manifest lives on a CDN/repo URL that was restructured; GitHub raw rate limits after many dev reloads (cache:'no-cache' defeats browser caching, amplifying request volume); corporate proxy blocking the host; first run with no snapshot cached so any network hiccup becomes user-visible.","solutions":["curl -I the manifest URL and check the status; 404 means update MASCOT_MANIFEST_URL, 403 means rate limiting (wait or switch to a pinned commit URL), 5xx means upstream outage — retry later.","If behind a proxy, allowlist the manifest host or serve the manifest locally in dev.","Ship/seed a snapshot or bundled manifest so first-run users degrade gracefully instead of seeing the error.","Reduce no-cache pressure in dev by pointing MASCOT_MANIFEST_URL at a local static server."],"exampleFix":"// before\nconst res = await fetch(MASCOT_MANIFEST_URL, { cache: 'no-cache', signal: controller.signal });\nif (!res.ok) throw new Error(`manifest fetch failed (${res.status})`);\n\n// after — retry once on 5xx before falling back\nlet res = await fetch(MASCOT_MANIFEST_URL, { cache: 'no-cache', signal: controller.signal });\nif (res.status >= 500) {\n  await delay(1500);\n  res = await fetch(MASCOT_MANIFEST_URL, { cache: 'no-cache', signal: controller.signal });\n}\nif (!res.ok) throw new Error(`manifest fetch failed (${res.status})`);","handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  const manifest = await fetchMascotManifest();\n} catch (err) {\n  // fetchMascotManifest already falls back to snapshot when present;\n  // ensure one is seeded on first run or bundle a default manifest.\n}","preventionTips":["Seed/bundle a default manifest so first-run never depends on the network.","Retry transient 5xx once with backoff before surfacing failure.","Monitor the manifest URL (status + shape) with a synthetic check."],"tags":["mascot","manifest","http","network","fetch"],"backgroundTag":"http-request-failed","analyzedSha":"749120085864ce16e0f273c7b86fac7740b39c5b","analyzedAt":"2026-08-17T21:21:45.363Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}