moeru-ai/airi · error · Error
Spine ZIP must contain a .skel or .json skeleton file paired
Error message
Spine ZIP must contain a .skel or .json skeleton file paired with a .atlas
What it means
`detectSpineLayout(entries, atlasText)` returns the first matched skeleton+atlas pair by calling `detectAllSpineLayouts`. If no variant is found (`variants.length === 0`) it throws `Spine ZIP must contain a .skel or .json skeleton file paired with a .atlas`. It requires a same-basename skeleton (`.skel` or `.json`) alongside an `.atlas`/`.atlas.txt`.
Source
Thrown at packages/stage-ui-spine/src/utils/spine-zip-loader.ts:106
function dirname(path: string) {
const slash = path.lastIndexOf('/')
return slash === -1 ? '' : path.slice(0, slash + 1)
}
/**
* Inspect a Spine ZIP and resolve the skeleton, atlas, and texture page
* paths.
*
* Heuristics:
* 1. Find all `.atlas`/`.atlas.txt` files paired with same-basename skeletons.
* 2. For each pair, walk the atlas to extract texture page filenames.
* 3. Return the first matched pair as the primary layout.
*/
export function detectSpineLayout(entries: Record<string, string>, atlasText: Record<string, string>): SpineModelLayout {
const variants = detectAllSpineLayouts(entries, atlasText)
if (variants.length === 0)
throw new Error('Spine ZIP must contain a .skel or .json skeleton file paired with a .atlas')
return variants[0].layout
}
/**
* Detects all skeleton+atlas pairs in a ZIP, returning them as named
* variants. Useful for ZIPs containing multiple outfits/characters in
* separate folders.
*/
export function detectAllSpineLayouts(entries: Record<string, string>, atlasText: Record<string, string>): SpineModelVariant[] {
const allFiles = Object.keys(entries)
const atlasCandidates = allFiles.filter(isAtlasPath)
if (atlasCandidates.length === 0)
throw new Error('Spine ZIP must contain a .atlas (or .atlas.txt) file')
const variants: SpineModelVariant[] = []
const usedAtlases = new Set<string>()
// First pass: match each atlas with a same-basename skeleton.View on GitHub (pinned to 27111382b4)
Solutions
- Verify the archive has a `<name>.atlas` (or `.atlas.txt`) paired with a `<name>.skel` or `<name>.json` of the same basename in the same folder.
- Re-export from Spine with 'Texture packer' producing the `.atlas` alongside the skeleton.
- Rename files so skeleton and atlas basenames match exactly.
- If multiple characters exist, use `detectAllSpineLayouts` to inspect available pairs.
Example fix
// before
const layout = detectSpineLayout(entries, atlasText) // throws
// after
const variants = detectAllSpineLayouts(entries, atlasText)
if (!variants.length) throw new Error('No skeleton+atlas pair with matching basename found')
const layout = variants[0].layout Defensive patterns
Strategy: validation
Validate before calling
const variants = detectAllSpineLayouts(entries, atlasText)
if (!variants.length)
throw new Error('No skeleton+atlas pair with matching basename')
const layout = variants[0].layout Type guard
function hasSpinePair(entries: Record<string, string>, atlasText: Record<string, string>): boolean {
return detectAllSpineLayouts(entries, atlasText).length > 0
} Try / catch
try {
detectSpineLayout(entries, atlasText)
} catch (e) {
if (e instanceof Error && e.message.includes('paired with a .atlas')) {
// inspect available files and re-prompt
} else throw e
} Prevention
- Keep skeleton (.skel/.json) and .atlas in the same folder with matching basenames.
- Use detectAllSpineLayouts to inspect multi-character archives.
- Re-export from Spine with the texture packer producing the atlas.
When it happens
Trigger: Calling `detectSpineLayout` with an `entries` map that has an atlas but no matching skeleton, a skeleton but no atlas, or skeleton/atlas with mismatched basenames (`hero.skel` + `character.atlas`). Also when `entries` is empty or only contains textures.
Common situations: Spine archive missing the `.atlas` or the `.skel`/`.json`; renamed files breaking the basename pairing; multiple characters in one archive with only one fully paired; a Spine Lite vs Spine Pro export where the skeleton extension differs.
Related errors
- Spine ZIP must contain a .atlas (or .atlas.txt) file
- Spine ZIP must contain at least one skeleton+atlas pair
- Empty settings file: ${url}
- Expected exactly one moc file, got ${mocFiles.length} ${file
- MMD ZIP must contain a .pmx or .pmd model file
AI-assisted analysis of moeru-ai/airi@27111382b4 (2026-08-12).
Data as JSON: /api/errors/259a51fa57c5a949.
Report an issue: GitHub.