tinyhumansai/openhuman · warning

failed to fetch riv (${res.status}) from ${url}

Error message

failed to fetch riv (${res.status}) from ${url}

What it means

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.

Source

Thrown at app/src/features/human/Mascot/rivCache.ts:93

}

function writeEntry(db: IDBDatabase, entry: RivCacheEntry): Promise<void> {
  return new Promise(resolve => {
    try {
      const tx = db.transaction(STORE, 'readwrite');
      tx.objectStore(STORE).put(entry);
      tx.oncomplete = () => resolve();
      tx.onerror = () => resolve();
      tx.onabort = () => resolve();
    } catch {
      resolve();
    }
  });
}

async function fetchBuffer(url: string): Promise<ArrayBuffer> {
  const res = await fetch(url);
  if (!res.ok) throw new Error(`failed to fetch riv (${res.status}) from ${url}`);
  return res.arrayBuffer();
}

/**
 * Resolve the .riv binary for a mascot, hitting the network only when the
 * cached version differs from `version`. Returns an ArrayBuffer suitable for
 * `useRive({ buffer })`.
 */
export async function loadRivBuffer(
  id: string,
  version: string,
  url: string
): Promise<ArrayBuffer> {
  const mem = memCache.get(id);
  if (mem && mem.version === version) {
    cacheLog('mem hit %s@%s', id, version);
    return mem.buffer;
  }

View on GitHub (pinned to 7491200858)

Solutions

  1. 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.
  2. If the manifest is stale, clear the cached manifest snapshot so fetchMascotManifest() refetches a consistent manifest+URLs pair.
  3. Catch per-mascot render errors and fall back to a bundled/default mascot so one broken asset does not break the feature.
  4. Verify the version string passed to loadRivBuffer matches the manifest's version to avoid needless cache-miss refetches.

Example fix

// before
try {
  buffer = await loadRivBuffer(id, version, url);
} catch (err) {
  throw err;
}

// after
try {
  buffer = await loadRivBuffer(id, version, url);
} catch (err) {
  warnLog('mascot asset unavailable, using default', { id, err });
  buffer = await loadRivBuffer(DEFAULT_MASCOT_ID, DEFAULT_MASCOT_VERSION, DEFAULT_MASCOT_URL);
}
Defensive patterns

Strategy: fallback

Validate before calling

async function headOk(url: string): Promise<boolean> {
  try { const r = await fetch(url, { method: 'HEAD' }); return r.ok; } catch { return false; }
}
if (!(await headOk(url))) { /* use default mascot asset */ }

Try / catch

try {
  buffer = await loadRivBuffer(id, version, url);
} catch (err) {
  if (err instanceof Error && err.message.includes('failed to fetch riv')) {
    buffer = await loadRivBuffer(DEFAULT_ID, DEFAULT_VERSION, DEFAULT_URL);
  } else throw err;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of tinyhumansai/openhuman@7491200858 (2026-08-17). Data as JSON: /api/errors/9ebae57a0edac126. Report an issue: GitHub.