{"record":{"id":"0b06f5e879a46d78","repo":"stablyai/orca","slug":"spritesheet-path-is-not-a-file","errorCode":null,"errorMessage":"Spritesheet path is not a file.","messagePattern":"Spritesheet path is not a file\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/main/ipc/pet.ts","lineNumber":319,"sourceCode":"    if (!cmp(sheetSrc + sep).startsWith(cmp(bundleRoot))) {\n      throw new Error('spritesheetPath escapes the bundle.')\n    }\n    if (await isSymlink(sheetSrc)) {\n      throw new Error('spritesheet must not be a symlink.')\n    }\n    const sheetClass = classifyFile(sheetSrc)\n    if (!sheetClass || sheetClass.ext === '.svg') {\n      // SVG can't be used as a sprite sheet (no pixel grid).\n      throw new Error('Spritesheet must be a PNG, APNG, JPG, GIF, or WebP.')\n    }\n    let sheetStat: Awaited<ReturnType<typeof stat>>\n    try {\n      sheetStat = await stat(sheetSrc)\n    } catch {\n      throw new Error('Spritesheet file not found.')\n    }\n    if (!sheetStat.isFile()) {\n      throw new Error('Spritesheet path is not a file.')\n    }\n    if (sheetStat.size > MAX_BYTES) {\n      throw new Error(\n        `Spritesheet is too large (${(sheetStat.size / (1024 * 1024)).toFixed(1)} MB).`\n      )\n    }\n\n    let sprite: NonNullable<CustomPet['sprite']> | undefined\n    if (manifest.frame) {\n      // Why: only decode when a frame layout needs validating — nativeImage can fail on some WebP variants in headless contexts.\n      const sheetBuf = await readFile(sheetSrc)\n      // Why: defend against TOCTOU — file may have grown between stat and read.\n      if (sheetBuf.byteLength > MAX_BYTES) {\n        throw new Error('Spritesheet exceeded the size limit.')\n      }\n      const dims = await readSheetDimensions(sheetBuf)\n      if (!dims) {\n        throw new Error('Could not decode the spritesheet image.')","sourceCodeStart":301,"sourceCodeEnd":337,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/src/main/ipc/pet.ts#L301-L337","documentation":"Thrown at pet.ts:318-319 when stat(sheetSrc) succeeds but sheetStat.isFile() is false — the path resolves to a directory, device, FIFO, socket, or block special file rather than a regular file. This is distinct from 1246 (stat threw) and catches the case where the path exists but is the wrong kind of inode.","triggerScenarios":"spritesheetPath names a subdirectory or a special file inside the bundle; the bundle was assembled so that 'spritesheet.png' is actually a folder containing frames.","commonSituations":"Bundle author pointed spritesheetPath at a directory of frames instead of a single sheet; a leftover FIFO/socket from a botched extraction.","solutions":["Point spritesheetPath at a single regular image file, not a directory.","If frames are separate images, combine them into a single sprite sheet first."],"exampleFix":"// before\n{ \"spritesheetPath\": \"frames/\" }  // a directory\n// after — combine frames into one sheet, then reference it\n{ \"spritesheetPath\": \"spritesheet.png\" }","handlingStrategy":"validation","validationCode":"import { stat } from 'node:fs/promises'\nasync function assertSheetIsRegularFile(p: string) {\n  const st = await stat(p)\n  if (!st.isFile()) throw new Error(`${p} is not a regular file`)\n}","typeGuard":null,"tryCatchPattern":"try { await importPetBundle(p) }\ncatch (e) { if (e instanceof Error && e.message === 'Spritesheet path is not a file.') { /* point at a file, not a dir */ } else throw e }","preventionTips":["Point spritesheetPath at a single image file, never a directory of frames.","Combine per-frame images into one sprite sheet before bundling."],"tags":["pet-bundle","filesystem","validation"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}