moeru-ai/airi · error · Error

Empty settings file: ${url}

Error message

Empty settings file: ${url}

What it means

`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.

Source

Thrown at packages/stage-ui-live2d/src/utils/live2d-zip-loader.ts:130

function normalizeLive2DArchivePath(path: string): string {
  try {
    return decodeURI(path)
  }
  catch {
    // Malformed percent escapes cannot be URI-decoded and therefore represent a literal archive path.
    return path
  }
}

function useArchivePathResolution(settings: ModelSettings): ModelSettings {
  const resolveURL = settings.resolveURL.bind(settings)
  settings.resolveURL = path => normalizeLive2DArchivePath(resolveURL(path))
  return settings
}

function createModelSettings(text: string, url: string): ModelSettings {
  if (!text) {
    throw new Error(`Empty settings file: ${url}`)
  }

  const settingsJSON = JSON.parse(text) as JSONObject & { url?: string }
  settingsJSON.url = url
  const runtime = Live2DFactory.findRuntime(settingsJSON)

  if (!runtime) {
    throw new Error('Unknown settings JSON')
  }

  return useArchivePathResolution(runtime.createModelSettings(settingsJSON))
}

export function isSettingsFile(file: string) {
  return !shouldIgnoreLive2DArchiveEntry(file)
    && !file.endsWith('items_pinned_to_model.json')
    && (file.endsWith('.model3.json') || file.endsWith('.model.json'))
}

View on GitHub (pinned to 27111382b4)

Solutions

  1. Re-download or re-export the model from its source tool.
  2. Verify the archive before loading: check that the settings entry has non-zero size (`zip.file(path)?._data?.uncompressedSize`).
  3. Validate the ZIP integrity (e.g. `unzip -t`) and confirm the settings file is not empty.
  4. Catch the error and prompt the user to pick a different/complete archive.

Example fix

// before
const settings = await ZipLoader.loadAsync(blob).then(z => createModelSettingsFromZip(z))

// after
const zip = await JSZip.loadAsync(blob)
const entry = zip.file(settingsPath)
if (!entry) throw new Error(`Missing ${settingsPath} in archive`)
const text = await entry.async('text')
if (!text.trim()) throw new Error(`${settingsPath} is empty; archive may be corrupt`)
const settings = createModelSettings(text, settingsPath)
Defensive patterns

Strategy: validation

Validate before calling

const text = await zipEntry.async('text')
if (!text || !text.trim())
  throw new Error(`${path} is empty; re-export or re-download the model`)
const settings = createModelSettings(text, path)

Type guard

function isNonEmptySettingsText(text: unknown): text is string {
  return typeof text === 'string' && text.trim().length > 0
}

Try / catch

try {
  await loadLive2DFromArchive(blob)
} catch (e) {
  if (e instanceof Error && e.message.startsWith('Empty settings file')) {
    // tell user the manifest is empty; offer re-download
  } else throw e
}

Prevention

When it happens

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

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

Related errors


AI-assisted analysis of moeru-ai/airi@27111382b4 (2026-08-12). Data as JSON: /api/errors/2e9758c851ef2f53. Report an issue: GitHub.