{"record":{"id":"3680c048328c9b09","repo":"stablyai/orca","slug":"invalid-pet-json-error-instanceof-error-error","errorCode":null,"errorMessage":"Invalid pet.json: ${error instanceof Error ? error.message : 'parse error'}","messagePattern":"Invalid pet\\.json: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/main/ipc/pet.ts","lineNumber":280,"sourceCode":"      throw new Error('Bundle is missing pet.json.')\n    }\n    if (!manifestStat.isFile() || manifestStat.size > MAX_MANIFEST_BYTES) {\n      throw new Error('pet.json is invalid.')\n    }\n    if (await isSymlink(manifestPath)) {\n      throw new Error('pet.json must not be a symlink.')\n    }\n\n    let manifest: ResolvedPetManifest<PetManifest>\n    try {\n      const raw = await readFile(manifestPath, 'utf8')\n      // Why: defend against TOCTOU — the file may have grown between the stat check and this read.\n      if (Buffer.byteLength(raw, 'utf8') > MAX_MANIFEST_BYTES) {\n        throw new Error('pet.json exceeded the manifest size limit.')\n      }\n      manifest = applyCodexPetDefaults(PetManifestSchema.parse(JSON.parse(raw)))\n    } catch (error) {\n      throw new Error(`Invalid pet.json: ${error instanceof Error ? error.message : 'parse error'}`)\n    }\n\n    // Why: spritesheetPath is bundle-relative and attacker-controlled — reject absolute/escaping paths (and symlinks) so a bundle can't reach outside.\n    const normalizedSpritePath = manifest.spritesheetPath.replace(/[\\\\/]+/g, sep)\n    if (\n      isAbsolute(manifest.spritesheetPath) ||\n      isAbsolute(normalizedSpritePath) ||\n      /^[a-zA-Z]:/.test(manifest.spritesheetPath)\n    ) {\n      throw new Error('spritesheetPath must be relative to the bundle.')\n    }\n    // Why: bundles exported on Windows may be imported on macOS/Linux; normalize separators before resolving.\n    const sheetSrc = resolve(bundleDir, normalizedSpritePath)\n    const bundleResolved = resolve(bundleDir)\n    if (sheetSrc === bundleResolved) {\n      throw new Error('spritesheetPath must point to a file, not the bundle root.')\n    }\n    const bundleRoot = bundleResolved + sep","sourceCodeStart":262,"sourceCodeEnd":298,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/src/main/ipc/pet.ts#L262-L298","documentation":"Thrown when importing a pet bundle whose pet.json cannot be read, parsed as JSON, or validated against PetManifestSchema (Zod). It is a catch-all wrapper at pet.ts:278-281 that re-wraps any failure from readFile, JSON.parse, the MAX_MANIFEST_BYTES (64 KB) TOCTOU guard, or PetManifestSchema.parse into a single 'Invalid pet.json' message, appending the inner error's message when available. The inner cause is preserved in the message suffix, which is the real diagnostic.","triggerScenarios":"Calling the pet:import IPC handler (or whichever path invokes the bundle-import function) with a directory whose pet.json is truncated/malformed JSON, references unknown keys in strict mode, has a field outside Zod bounds (e.g. id longer than 128 chars, spritesheetPath with a NUL byte or '..'), or a raw file larger than MAX_MANIFEST_BYTES (64*1024) that grew between the earlier stat check and the readFile at line 273.","commonSituations":"Hand-edited pet.json with a trailing comma or unquoted value; bundle exported by a generator emitting a field the schema doesn't expect combined with a future schema tightening; manifest accidentally including megabytes of base64 metadata; encoding issue (UTF-16 BOM) making JSON.parse throw; a partially written file from a crashed export.","solutions":["Read the suffix after the colon — it is the inner Zod/error message and names the exact failing field (e.g. 'spritesheetPath: invalid spritesheetPath'). Fix that field in pet.json.","Validate the manifest locally before import: const PetManifestSchema requires id/description/spritesheetPath bounds listed at pet.ts:77-113; run it through a JSON linter.","Confirm pet.json is under 64 KB and contains no NUL bytes, leading '/', '\\', or '..' segments in spritesheetPath.","If the file was mid-write, re-export the bundle and retry the import."],"exampleFix":"// before — pet.json with a trailing comma and oversized id\n{\n  \"id\": \"<128+ chars>\",\n  \"displayName\": \"Cat\",\n  \"spritesheetPath\": \"cat.png\",\n}\n// after — valid shape within Zod bounds\n{\n  \"displayName\": \"Cat\",\n  \"spritesheetPath\": \"cat.png\",\n  \"frame\": { \"width\": 32, \"height\": 32 }\n}","handlingStrategy":"validation","validationCode":"// Validate pet.json against the same shape before invoking the import IPC.\nimport { z } from 'zod'\nconst PetManifestSchema = z.object({\n  id: z.string().min(1).max(128).optional(),\n  displayName: z.string().min(1).max(120).optional(),\n  description: z.string().max(2000).optional(),\n  spritesheetPath: z.string().min(1).max(255)\n    .refine((p) => !p.includes('\\0') && !p.startsWith('/') && !p.startsWith('\\\\') && !p.includes('..'), 'invalid spritesheetPath')\n    .optional(),\n  frame: z.object({ width: z.number().int().positive().max(1024), height: z.number().int().positive().max(1024) }).optional(),\n  fps: z.number().positive().max(60).optional(),\n  defaultAnimation: z.string().min(1).max(64).optional(),\n  animations: z.record(z.string().min(1).max(64), z.object({\n    row: z.number().int().min(0).max(256),\n    frames: z.number().int().positive().max(512),\n    frameDurationsMs: z.array(z.number().positive().max(60_000)).max(512).optional()\n  })).optional()\n}).loose()\n\nasync function preflightManifest(path: string) {\n  const raw = await readFile(path, 'utf8')\n  if (Buffer.byteLength(raw, 'utf8') > 64 * 1024) throw new Error('manifest too large')\n  return PetManifestSchema.parse(JSON.parse(raw))\n}","typeGuard":"function isPetManifest(v: unknown): v is PetManifest {\n  return typeof v === 'object' && v !== null &&\n    (v.spritesheetPath === undefined || (typeof v.spritesheetPath === 'string' && v.spritesheetPath.length <= 255))\n}","tryCatchPattern":"try {\n  await importPetBundle(pickedPath)\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith('Invalid pet.json:')) {\n    // surface the inner cause (after the colon) to the user for the specific field fix\n    showError(e.message.slice('Invalid pet.json:'.length).trim())\n  } else throw e\n}","preventionTips":["Run pet.json through a JSON linter before bundling.","Have your bundle export tooling emit a manifest validated by PetManifestSchema so import never surprises.","Keep pet.json under 64 KB; do not embed base64 image data in it."],"tags":["pet-bundle","validation","zod","json","ipc"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}