stablyai/orca · error · Error

Could not save the pet bundle.

Error message

Could not save the pet bundle.

What it means

Thrown at pet.ts:389-398 when the atomic bundle save fails: the import stages files into a sibling .tmp directory (lines 388-394) then renames it into place. If rm, mkdir, copyFileNoFollow, or rename throws, the catch cleans up the .tmp directory and re-throws this generic message. The original error is swallowed (no logging), so the exact cause (disk full, EPERM, antivirus lock, cross-device rename) is not surfaced.

Source

Thrown at src/main/ipc/pet.ts:397

    // Why: always a fresh UUID (not the manifest's display-hint id) to avoid collisions, unsafe ids, and re-import clobbering.
    const id = randomUUID()
    const root = getPetsDir()
    await mkdir(root, { recursive: true })
    const destDir = join(root, id)
    const sheetExt = sheetClass.ext
    const sheetFileName = `spritesheet${sheetExt}`
    // Why: stage into a sibling .tmp then atomically rename, so a mid-copy failure can't leave a half-imported bundle.
    const tmpDir = `${destDir}.tmp`
    try {
      await rm(tmpDir, { recursive: true, force: true }).catch(() => {})
      await mkdir(tmpDir, { recursive: true })
      await copyFileNoFollow(sheetSrc, join(tmpDir, sheetFileName))
      await copyFileNoFollow(manifestPath, join(tmpDir, 'pet.json'))
      await rename(tmpDir, destDir)
    } catch {
      await rm(tmpDir, { recursive: true, force: true }).catch(() => {})
      throw new Error('Could not save the pet bundle.')
    }

    const rawLabel = (manifest.displayName ?? manifest.id ?? basename(bundleDir)).trim()
    const label = rawLabel.length > 0 ? rawLabel.slice(0, 40) : 'Pet bundle'
    return {
      id,
      label,
      fileName: sheetFileName,
      mimeType: sheetClass.mimeType,
      kind: 'bundle',
      sprite,
      // Why: renderer falls back to spriteFps when sprite is undefined (detected-frame bundles).
      ...(manifest.fps !== undefined ? { spriteFps: manifest.fps } : {})
    }
  })

  ipcMain.handle(
    'pet:read',

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Check free disk space on the volume holding userData (app.getPath('userData')).
  2. On Windows, temporarily disable/reconfigure antivirus that may lock the rename, or add an exclusion for the userData directory.
  3. Verify write permissions on the userData/sidekicks/custom directory.
  4. Manually remove any leftover <destDir>.tmp directory and retry.
  5. If on a multi-volume setup, ensure the userData directory is not on a read-only mount.
Defensive patterns

Strategy: try-catch

Validate before calling

import { access, constants } from 'node:fs/promises'
import { app } from 'electron'
async function assertPetsDirWritable() {
  const dir = app.getPath('userData')
  await access(dir, constants.W_OK)
}

Try / catch

try { await importPetBundle(p) }
catch (e) {
  if (e instanceof Error && e.message === 'Could not save the pet bundle.') {
    // surface a user-facing message: check disk space / permissions / antivirus
    showError('Could not save the pet bundle. Check disk space and permissions, then retry.')
  } else throw e
}

Prevention

When it happens

Trigger: Disk full during copyFileNoFollow; EPERM/permission denied writing to getPetsDir() (userData/sidekicks/custom); antivirus or file lock blocking the rename on Windows; cross-device rename where the .tmp dir and destDir are on different filesystems; the userData path is read-only.

Common situations: Windows antivirus locking the renamed directory; a managed/locked userData directory (corporate machine); low disk space; a stale .tmp from a previous failed import that rm cannot remove (permissions).

Related errors


AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12). Data as JSON: /api/errors/47b466ea771b33f1. Report an issue: GitHub.