stablyai/orca · error · Error
Invalid pet.json: ${error instanceof Error ? error.message :
Error message
Invalid pet.json: ${error instanceof Error ? error.message : 'parse error'} What it means
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.
Source
Thrown at src/main/ipc/pet.ts:280
throw new Error('Bundle is missing pet.json.')
}
if (!manifestStat.isFile() || manifestStat.size > MAX_MANIFEST_BYTES) {
throw new Error('pet.json is invalid.')
}
if (await isSymlink(manifestPath)) {
throw new Error('pet.json must not be a symlink.')
}
let manifest: ResolvedPetManifest<PetManifest>
try {
const raw = await readFile(manifestPath, 'utf8')
// Why: defend against TOCTOU — the file may have grown between the stat check and this read.
if (Buffer.byteLength(raw, 'utf8') > MAX_MANIFEST_BYTES) {
throw new Error('pet.json exceeded the manifest size limit.')
}
manifest = applyCodexPetDefaults(PetManifestSchema.parse(JSON.parse(raw)))
} catch (error) {
throw new Error(`Invalid pet.json: ${error instanceof Error ? error.message : 'parse error'}`)
}
// Why: spritesheetPath is bundle-relative and attacker-controlled — reject absolute/escaping paths (and symlinks) so a bundle can't reach outside.
const normalizedSpritePath = manifest.spritesheetPath.replace(/[\\/]+/g, sep)
if (
isAbsolute(manifest.spritesheetPath) ||
isAbsolute(normalizedSpritePath) ||
/^[a-zA-Z]:/.test(manifest.spritesheetPath)
) {
throw new Error('spritesheetPath must be relative to the bundle.')
}
// Why: bundles exported on Windows may be imported on macOS/Linux; normalize separators before resolving.
const sheetSrc = resolve(bundleDir, normalizedSpritePath)
const bundleResolved = resolve(bundleDir)
if (sheetSrc === bundleResolved) {
throw new Error('spritesheetPath must point to a file, not the bundle root.')
}
const bundleRoot = bundleResolved + sepView on GitHub (pinned to 1136503c6a)
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.
Example fix
// before — pet.json with a trailing comma and oversized id
{
"id": "<128+ chars>",
"displayName": "Cat",
"spritesheetPath": "cat.png",
}
// after — valid shape within Zod bounds
{
"displayName": "Cat",
"spritesheetPath": "cat.png",
"frame": { "width": 32, "height": 32 }
} Defensive patterns
Strategy: validation
Validate before calling
// Validate pet.json against the same shape before invoking the import IPC.
import { z } from 'zod'
const PetManifestSchema = z.object({
id: z.string().min(1).max(128).optional(),
displayName: z.string().min(1).max(120).optional(),
description: z.string().max(2000).optional(),
spritesheetPath: z.string().min(1).max(255)
.refine((p) => !p.includes('\0') && !p.startsWith('/') && !p.startsWith('\\') && !p.includes('..'), 'invalid spritesheetPath')
.optional(),
frame: z.object({ width: z.number().int().positive().max(1024), height: z.number().int().positive().max(1024) }).optional(),
fps: z.number().positive().max(60).optional(),
defaultAnimation: z.string().min(1).max(64).optional(),
animations: z.record(z.string().min(1).max(64), z.object({
row: z.number().int().min(0).max(256),
frames: z.number().int().positive().max(512),
frameDurationsMs: z.array(z.number().positive().max(60_000)).max(512).optional()
})).optional()
}).loose()
async function preflightManifest(path: string) {
const raw = await readFile(path, 'utf8')
if (Buffer.byteLength(raw, 'utf8') > 64 * 1024) throw new Error('manifest too large')
return PetManifestSchema.parse(JSON.parse(raw))
} Type guard
function isPetManifest(v: unknown): v is PetManifest {
return typeof v === 'object' && v !== null &&
(v.spritesheetPath === undefined || (typeof v.spritesheetPath === 'string' && v.spritesheetPath.length <= 255))
} Try / catch
try {
await importPetBundle(pickedPath)
} catch (e) {
if (e instanceof Error && e.message.startsWith('Invalid pet.json:')) {
// surface the inner cause (after the colon) to the user for the specific field fix
showError(e.message.slice('Invalid pet.json:'.length).trim())
} else throw e
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- [verify-packaged-plugin-resources] invalid ${label} at ${pat
- Invalid accounts snapshot from host
- Codex reset attempt idempotency key is invalid
- emulator_helper_failed
- Selected file is not a markdown document.
AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12).
Data as JSON: /api/errors/3680c048328c9b09.
Report an issue: GitHub.