{"record":{"id":"9ebae57a0edac126","repo":"tinyhumansai/openhuman","slug":"failed-to-fetch-riv-res-status-from-url","errorCode":null,"errorMessage":"failed to fetch riv (${res.status}) from ${url}","messagePattern":"failed to fetch riv \\((.+?)\\) from (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"app/src/features/human/Mascot/rivCache.ts","lineNumber":93,"sourceCode":"}\n\nfunction writeEntry(db: IDBDatabase, entry: RivCacheEntry): Promise<void> {\n  return new Promise(resolve => {\n    try {\n      const tx = db.transaction(STORE, 'readwrite');\n      tx.objectStore(STORE).put(entry);\n      tx.oncomplete = () => resolve();\n      tx.onerror = () => resolve();\n      tx.onabort = () => resolve();\n    } catch {\n      resolve();\n    }\n  });\n}\n\nasync function fetchBuffer(url: string): Promise<ArrayBuffer> {\n  const res = await fetch(url);\n  if (!res.ok) throw new Error(`failed to fetch riv (${res.status}) from ${url}`);\n  return res.arrayBuffer();\n}\n\n/**\n * Resolve the .riv binary for a mascot, hitting the network only when the\n * cached version differs from `version`. Returns an ArrayBuffer suitable for\n * `useRive({ buffer })`.\n */\nexport async function loadRivBuffer(\n  id: string,\n  version: string,\n  url: string\n): Promise<ArrayBuffer> {\n  const mem = memCache.get(id);\n  if (mem && mem.version === version) {\n    cacheLog('mem hit %s@%s', id, version);\n    return mem.buffer;\n  }","sourceCodeStart":75,"sourceCodeEnd":111,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/749120085864ce16e0f273c7b86fac7740b39c5b/app/src/features/human/Mascot/rivCache.ts#L75-L111","documentation":"rivCache.ts caches .riv (Rive) binary assets in IndexedDB. fetchBuffer() performs the plain network fetch for a mascot's animation file and throws this error when the response status is not ok, embedding both the HTTP status and the full URL for diagnosis. Callers hit this inside loadRivBuffer() when the cached version is absent or differs from the requested version, forcing a network fetch.","triggerScenarios":"loadRivBuffer(id, version, url) with a cache miss or stale cache issues fetch(url) for the .riv asset and the CDN returns 404/403/5xx. Typical when the manifest lists an asset URL that no longer exists, the asset was renamed, the CDN deploy is partial, or an entry points at a different branch/commit that lacks the file.","commonSituations":"Mascot repo restructure after the manifest was cached (stale manifest + fresh assets or vice versa); GitHub raw rate limiting during development; version mismatch causing repeated cache-busting fetches of a deleted file; typo in the per-entry URL inside the manifest.","solutions":["Copy the exact URL from the error message and curl it — 404 means fix the manifest entry or the asset location; 403/429 means rate limiting, back off or pin to a release CDN.","If the manifest is stale, clear the cached manifest snapshot so fetchMascotManifest() refetches a consistent manifest+URLs pair.","Catch per-mascot render errors and fall back to a bundled/default mascot so one broken asset does not break the feature.","Verify the version string passed to loadRivBuffer matches the manifest's version to avoid needless cache-miss refetches."],"exampleFix":"// before\ntry {\n  buffer = await loadRivBuffer(id, version, url);\n} catch (err) {\n  throw err;\n}\n\n// after\ntry {\n  buffer = await loadRivBuffer(id, version, url);\n} catch (err) {\n  warnLog('mascot asset unavailable, using default', { id, err });\n  buffer = await loadRivBuffer(DEFAULT_MASCOT_ID, DEFAULT_MASCOT_VERSION, DEFAULT_MASCOT_URL);\n}","handlingStrategy":"fallback","validationCode":"async function headOk(url: string): Promise<boolean> {\n  try { const r = await fetch(url, { method: 'HEAD' }); return r.ok; } catch { return false; }\n}\nif (!(await headOk(url))) { /* use default mascot asset */ }","typeGuard":null,"tryCatchPattern":"try {\n  buffer = await loadRivBuffer(id, version, url);\n} catch (err) {\n  if (err instanceof Error && err.message.includes('failed to fetch riv')) {\n    buffer = await loadRivBuffer(DEFAULT_ID, DEFAULT_VERSION, DEFAULT_URL);\n  } else throw err;\n}","preventionTips":["Validate asset URLs against the same manifest revision that produced them.","Cache aggressively (IndexedDB) so transient CDN failures are masked by prior fetches.","Fall back per-mascot so one broken asset never breaks the whole mascot feature."],"tags":["rive","mascot","asset","http","cache","indexeddb"],"backgroundTag":"asset-download-failed","analyzedSha":"749120085864ce16e0f273c7b86fac7740b39c5b","analyzedAt":"2026-08-17T21:21:45.363Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}