moeru-ai/airi · error · Error
Failed to fetch MMD model: ${response.status} ${response.sta
Error message
Failed to fetch MMD model: ${response.status} ${response.statusText} What it means
`loadMMDModelFromSource(src, options)` fetches the MMD model bytes via `fetch(src)`. If the response is not OK (`!response.ok`, i.e. HTTP status outside 200–299) it throws `Failed to fetch MMD model: <status> <statusText>`. The OPFSCache path is only taken when a cache hit exists, so a network/origin error surfaces here.
Source
Thrown at packages/stage-ui-mmd/src/utils/mmd-loader.ts:96
*
* Accepts either a packaged ZIP (the usual distribution form: model plus
* textures) or a bare `.pmx`/`.pmd` URL. ZIP archives are unpacked to blob
* URLs and a basename-based texture resolver is installed on the loader; raw
* URLs are loaded directly and rely on the server's relative paths.
*
* The returned `dispose()` revokes any blob URLs created during the load. It
* does not dispose the mesh's GPU resources — the scene owns that lifecycle.
*/
export async function loadMMDModelFromSource(src: string, options: LoadMMDOptions = {}): Promise<ResolvedMMDModel> {
const cachedSource = options.cacheKey ? await OPFSCache.get(options.cacheKey, src) : null
let buffer: ArrayBuffer
if (cachedSource) {
buffer = await cachedSource.arrayBuffer()
}
else {
const response = await fetch(src)
if (!response.ok)
throw new Error(`Failed to fetch MMD model: ${response.status} ${response.statusText}`)
buffer = await response.arrayBuffer()
}
if (isZip(buffer)) {
const assets = await loadMMDZip(buffer)
let mmd: MMD | undefined
try {
const { loader, manager } = createMMDLoaderContext(assets.urlModifier)
mmd = await loadMMD(loader, assets.modelBlobUrl)
prepareMMDMaterials(mmd.mesh)
if (options.waitForTextures)
await waitForManagerIdle(manager)
if (options.cacheKey && !cachedSource)
await OPFSCache.save(options.cacheKey, new Blob([buffer]), src)
return {
mmd,
mesh: mmd.mesh,
format: assets.variant.format,View on GitHub (pinned to 27111382b4)
Solutions
- Verify the `src` URL resolves in a browser/curl and returns the expected `.pmx`/`.pmd`/`.zip` bytes with HTTP 200.
- Add CORS headers (`Access-Control-Allow-Origin`) on the hosting origin for the model path.
- Provide a working `options.cacheKey` so a previously cached copy is used when the network fails.
- Catch the error and offer a retry or a fallback model URL.
Example fix
// before
const mmd = await loadMMDModelFromSource(src)
// after
const res = await fetch(src, { method: 'HEAD' }).catch(() => null)
if (!res || !res.ok) throw new Error(`MMD source unreachable at ${src} (HTTP ${res?.status ?? 'no response'})`)
const mmd = await loadMMDModelFromSource(src, { cacheKey: `mmd:${src}` }) Defensive patterns
Strategy: retry
Validate before calling
async function isMMDSrcReachable(src: string): Promise<boolean> {
try {
const r = await fetch(src, { method: 'HEAD' })
return r.ok
} catch { return false }
}
if (await isMMDSrcReachable(src)) await loadMMDModelFromSource(src, { cacheKey: `mmd:${src}` }) Try / catch
let lastErr: unknown
for (const url of [src, fallbackSrc]) {
try {
return await loadMMDModelFromSource(url, { cacheKey: `mmd:${url}` })
} catch (e) {
lastErr = e
if (!(e instanceof Error && e.message.startsWith('Failed to fetch MMD model'))) throw e
}
}
throw lastErr Prevention
- Verify the model URL returns HTTP 200 before loading.
- Configure CORS on the model host.
- Pass options.cacheKey so OPFSCache can serve on transient failures.
- Provide a fallback URL for resilience.
When it happens
Trigger: A `src` URL that returns 404 (model not found), 403 (auth/CORS), 500 (server error), a wrong base URL, a CDN path typo, or a cross-origin resource without proper CORS headers returning an opaque/error response. Also a server temporarily down returning 502/503.
Common situations: Wrong asset URL after a deploy/rename; missing CORS configuration on the model host; expired signed URL; the model file was never uploaded; reverse proxy returning HTML error pages with HTTP 200 (those slip through but fail later) vs a real non-2xx.
Related errors
- Failed to fetch image: ${response.statusText}
- MMD ZIP must contain a .pmx or .pmd model file
- OpenRouter audio response has no body
- Streaming transcription response is missing a readable body.
- No candidate server channel URL was reachable. ${errors.join
AI-assisted analysis of moeru-ai/airi@27111382b4 (2026-08-12).
Data as JSON: /api/errors/b61c3cd09293ea1a.
Report an issue: GitHub.