{"record":{"id":"19f8732e08638e36","repo":"chatboxai/chatbox","slug":"staged-resource-is-missing-plan-resource-path","errorCode":null,"errorMessage":"Staged resource is missing: ${plan.resource.path}","messagePattern":"Staged resource is missing: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/renderer/packages/backup/import-backup.ts","lineNumber":108,"sourceCode":"    if (expectedPaths.has(descriptor.path)) throw new Error(`Manifest contains a duplicate path: ${descriptor.path}`)\n    expectedPaths.add(descriptor.path)\n    const staged = stagedEntries.get(descriptor.path)\n    if (!staged) throw new Error(`Backup entry is missing: ${descriptor.path}`)\n    if (staged.size !== descriptor.size) throw new Error(`Backup entry size mismatch: ${descriptor.path}`)\n    if (staged.checksum.value !== descriptor.checksum.value) {\n      throw new Error(`Backup entry checksum mismatch: ${descriptor.path}`)\n    }\n  }\n  for (const path of stagedEntries.keys()) {\n    if (!expectedPaths.has(path)) throw new Error(`Backup contains an entry not listed in manifest: ${path}`)\n  }\n  if (expectedPaths.size !== stagedEntries.size) throw new Error('Backup manifest entry list is incomplete')\n  validateBackupManifestGraph(manifest)\n}\n\nasync function readStagedResource(storage: BackupStorage, plan: Pick<ResourceWritePlan, 'resource' | 'tempKey'>) {\n  const base64 = await storage.getBlob(plan.tempKey)\n  if (base64 === null) throw new Error(`Staged resource is missing: ${plan.resource.path}`)\n  return decodeStoredBlob(base64ToBytes(base64), plan.resource.encoding, plan.resource.mimeType)\n}\n\nasync function findAvailableCollisionKey(storage: BackupStorage, originalKey: string, reserved: Set<string>) {\n  for (let attempt = 0; attempt < 100; attempt++) {\n    let candidatePrefix = 'resource:imported'\n    for (const prefix of ['picture:', 'file:', 'link:', 'parseFile-', 'parseUrl-']) {\n      if (originalKey.startsWith(prefix)) {\n        candidatePrefix = `${prefix}imported`\n        break\n      }\n    }\n    const candidate = `${candidatePrefix}:${uuidv4()}`\n    if (!reserved.has(candidate) && (await storage.getBlob(candidate)) === null) return candidate\n  }\n  throw new Error(`Could not allocate a resource key for: ${originalKey}`)\n}\n","sourceCodeStart":90,"sourceCodeEnd":126,"githubUrl":"https://github.com/chatboxai/chatbox/blob/81571269addb6bafb589a920b2883f1e1e084fd1/src/renderer/packages/backup/import-backup.ts#L90-L126","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Free storage quota before importing large backups.","Ensure no concurrent imports or cleanup routines run during import.","Use a persistent storage backend (request IndexedDB persistence to prevent eviction).","Retry the import in a clean session."],"exampleFix":"// before: temp blobs vanish between staging and commit\nawait importBackupArchive(file, options)\n// after: request persistence and verify free quota first\nif (navigator.storage?.persist) await navigator.storage.persist()\nconst estimate = await navigator.storage?.estimate()\nif (estimate && estimate.usage / estimate.quota > 0.9) {\n  throw new Error('Insufficient storage quota for import')\n}","handlingStrategy":"retry","validationCode":"// Before import, request persistence and ensure ample free quota so temp blobs survive.\nif (navigator.storage?.persist) await navigator.storage.persist()\nconst est = await navigator.storage?.estimate()\nif (est && est.quota && est.usage / est.quota > 0.9) {\n  throw new Error('Free storage before importing this backup')\n}","typeGuard":null,"tryCatchPattern":"let lastError: unknown\nfor (let attempt = 0; attempt < 2; attempt++) {\n  try {\n    return await importBackupArchive(file, options)\n  } catch (error) {\n    lastError = error\n    if (error instanceof Error && error.message.startsWith('Staged resource is missing')) continue\n    throw error\n  }\n}\nthrow lastError","preventionTips":["Request persistent storage so the browser does not evict blobs mid-import.","Prevent concurrent tabs from running cleanupTemps during an import.","For test mocks, ensure getBlob returns null for keys that were never written."],"tags":["backup","import","resource","blob-storage","quota","environmental"],"backgroundTag":null,"analyzedSha":"81571269addb6bafb589a920b2883f1e1e084fd1","analyzedAt":"2026-08-12T21:51:44.981Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}