{"record":{"id":"2e9758c851ef2f53","repo":"moeru-ai/airi","slug":"empty-settings-file-url","errorCode":null,"errorMessage":"Empty settings file: ${url}","messagePattern":"Empty settings file: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/stage-ui-live2d/src/utils/live2d-zip-loader.ts","lineNumber":130,"sourceCode":"function normalizeLive2DArchivePath(path: string): string {\n  try {\n    return decodeURI(path)\n  }\n  catch {\n    // Malformed percent escapes cannot be URI-decoded and therefore represent a literal archive path.\n    return path\n  }\n}\n\nfunction useArchivePathResolution(settings: ModelSettings): ModelSettings {\n  const resolveURL = settings.resolveURL.bind(settings)\n  settings.resolveURL = path => normalizeLive2DArchivePath(resolveURL(path))\n  return settings\n}\n\nfunction createModelSettings(text: string, url: string): ModelSettings {\n  if (!text) {\n    throw new Error(`Empty settings file: ${url}`)\n  }\n\n  const settingsJSON = JSON.parse(text) as JSONObject & { url?: string }\n  settingsJSON.url = url\n  const runtime = Live2DFactory.findRuntime(settingsJSON)\n\n  if (!runtime) {\n    throw new Error('Unknown settings JSON')\n  }\n\n  return useArchivePathResolution(runtime.createModelSettings(settingsJSON))\n}\n\nexport function isSettingsFile(file: string) {\n  return !shouldIgnoreLive2DArchiveEntry(file)\n    && !file.endsWith('items_pinned_to_model.json')\n    && (file.endsWith('.model3.json') || file.endsWith('.model.json'))\n}","sourceCodeStart":112,"sourceCodeEnd":148,"githubUrl":"https://github.com/moeru-ai/airi/blob/27111382b4a79a7e983289d6e983a06af185ed0f/packages/stage-ui-live2d/src/utils/live2d-zip-loader.ts#L112-L148","documentation":"`createModelSettings(text, url)` builds a Live2D `ModelSettings` from the raw text of a `.model3.json` / `.model.json` file. It throws `Empty settings file: <url>` when `text` is falsy (empty string) before `JSON.parse` is attempted. The `<url>` identifies which settings file was empty.","triggerScenarios":"The settings JSON file inside a Live2D ZIP/archive resolved to zero bytes: a truncated download, a corrupt archive entry, a zero-length `model3.json`, or `ZipLoader.readText` returning `''` for a settings entry.","commonSituations":"An interrupted download where the `.model3.json` is partially written; a ZIP that compressed a 0-byte placeholder file; an archive produced by a tool that wrote the moc/textures but left the manifest empty; a CDN/proxy returning an empty body with HTTP 200.","solutions":["Re-download or re-export the model from its source tool.","Verify the archive before loading: check that the settings entry has non-zero size (`zip.file(path)?._data?.uncompressedSize`).","Validate the ZIP integrity (e.g. `unzip -t`) and confirm the settings file is not empty.","Catch the error and prompt the user to pick a different/complete archive."],"exampleFix":"// before\nconst settings = await ZipLoader.loadAsync(blob).then(z => createModelSettingsFromZip(z))\n\n// after\nconst zip = await JSZip.loadAsync(blob)\nconst entry = zip.file(settingsPath)\nif (!entry) throw new Error(`Missing ${settingsPath} in archive`)\nconst text = await entry.async('text')\nif (!text.trim()) throw new Error(`${settingsPath} is empty; archive may be corrupt`)\nconst settings = createModelSettings(text, settingsPath)","handlingStrategy":"validation","validationCode":"const text = await zipEntry.async('text')\nif (!text || !text.trim())\n  throw new Error(`${path} is empty; re-export or re-download the model`)\nconst settings = createModelSettings(text, path)","typeGuard":"function isNonEmptySettingsText(text: unknown): text is string {\n  return typeof text === 'string' && text.trim().length > 0\n}","tryCatchPattern":"try {\n  await loadLive2DFromArchive(blob)\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith('Empty settings file')) {\n    // tell user the manifest is empty; offer re-download\n  } else throw e\n}","preventionTips":["Validate archive entry sizes are non-zero before reading.","Re-download interrupted model archives.","Check ZIP integrity before loading."],"tags":["live2d","model-loading","archive","input-validation"],"backgroundTag":null,"analyzedSha":"27111382b4a79a7e983289d6e983a06af185ed0f","analyzedAt":"2026-08-12T18:33:34.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}