chatboxai/chatbox · error · Error

Staged resource is missing: ${plan.resource.path}

Error message

Staged resource is missing: ${plan.resource.path}

What it means

Thrown by readStagedResource when the temporary blob written during the reading phase can no longer be read back via storage.getBlob(tempKey). The temp key was just written moments earlier during staging, so a null return means the storage backend dropped or lost the blob between write and read. Propagates; because commitStarted may be true, rollback runs.

Source

Thrown at src/renderer/packages/backup/import-backup.ts:108

    if (expectedPaths.has(descriptor.path)) throw new Error(`Manifest contains a duplicate path: ${descriptor.path}`)
    expectedPaths.add(descriptor.path)
    const staged = stagedEntries.get(descriptor.path)
    if (!staged) throw new Error(`Backup entry is missing: ${descriptor.path}`)
    if (staged.size !== descriptor.size) throw new Error(`Backup entry size mismatch: ${descriptor.path}`)
    if (staged.checksum.value !== descriptor.checksum.value) {
      throw new Error(`Backup entry checksum mismatch: ${descriptor.path}`)
    }
  }
  for (const path of stagedEntries.keys()) {
    if (!expectedPaths.has(path)) throw new Error(`Backup contains an entry not listed in manifest: ${path}`)
  }
  if (expectedPaths.size !== stagedEntries.size) throw new Error('Backup manifest entry list is incomplete')
  validateBackupManifestGraph(manifest)
}

async function readStagedResource(storage: BackupStorage, plan: Pick<ResourceWritePlan, 'resource' | 'tempKey'>) {
  const base64 = await storage.getBlob(plan.tempKey)
  if (base64 === null) throw new Error(`Staged resource is missing: ${plan.resource.path}`)
  return decodeStoredBlob(base64ToBytes(base64), plan.resource.encoding, plan.resource.mimeType)
}

async function findAvailableCollisionKey(storage: BackupStorage, originalKey: string, reserved: Set<string>) {
  for (let attempt = 0; attempt < 100; attempt++) {
    let candidatePrefix = 'resource:imported'
    for (const prefix of ['picture:', 'file:', 'link:', 'parseFile-', 'parseUrl-']) {
      if (originalKey.startsWith(prefix)) {
        candidatePrefix = `${prefix}imported`
        break
      }
    }
    const candidate = `${candidatePrefix}:${uuidv4()}`
    if (!reserved.has(candidate) && (await storage.getBlob(candidate)) === null) return candidate
  }
  throw new Error(`Could not allocate a resource key for: ${originalKey}`)
}

View on GitHub (pinned to 81571269ad)

Solutions

  1. Free storage quota before importing large backups.
  2. Ensure no concurrent imports or cleanup routines run during import.
  3. Use a persistent storage backend (request IndexedDB persistence to prevent eviction).
  4. Retry the import in a clean session.

Example fix

// before: temp blobs vanish between staging and commit
await importBackupArchive(file, options)
// after: request persistence and verify free quota first
if (navigator.storage?.persist) await navigator.storage.persist()
const estimate = await navigator.storage?.estimate()
if (estimate && estimate.usage / estimate.quota > 0.9) {
  throw new Error('Insufficient storage quota for import')
}
Defensive patterns

Strategy: retry

Validate before calling

// Before import, request persistence and ensure ample free quota so temp blobs survive.
if (navigator.storage?.persist) await navigator.storage.persist()
const est = await navigator.storage?.estimate()
if (est && est.quota && est.usage / est.quota > 0.9) {
  throw new Error('Free storage before importing this backup')
}

Try / catch

let lastError: unknown
for (let attempt = 0; attempt < 2; attempt++) {
  try {
    return await importBackupArchive(file, options)
  } catch (error) {
    lastError = error
    if (error instanceof Error && error.message.startsWith('Staged resource is missing')) continue
    throw error
  }
}
throw lastError

Prevention

When it happens

Trigger: During the restore/commit phase, storage.getBlob(plan.tempKey) returns null for a resource that was staged under '__chatbox_backup_import:<id>:resource:N' in the reading phase.

Common situations: IndexedDB quota pressure evicting blobs mid-import; a storage backend that does not persist blobs reliably (in-memory mock in tests); another tab/process running cleanup concurrently; private/incognito mode clearing storage mid-operation.

Related errors


AI-assisted analysis of chatboxai/chatbox@81571269ad (2026-08-12). Data as JSON: /api/errors/19f8732e08638e36. Report an issue: GitHub.