{"record":{"id":"dcf91d2caad93832","repo":"Stirling-Tools/Stirling-PDF","slug":"missing-file-data-for-stub-name-stub-id","errorCode":null,"errorMessage":"Missing file data for ${stub.name || stub.id}","messagePattern":"Missing file data for (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"frontend/editor/src/core/services/serverStorageBundle.ts","lineNumber":65,"sourceCode":"    stubs: Awaited<ReturnType<typeof fileStorage.getHistoryChainStubs>>;\n  }> = [];\n\n  for (const rootId of uniqueRoots) {\n    const stubs = await fileStorage.getHistoryChainStubs(rootId);\n    if (stubs.length === 0) {\n      throw new Error(\"No history chain found for file.\");\n    }\n    allStubs.push({ rootId, stubs });\n  }\n\n  const zip = new JSZip();\n  const entries: ShareBundleEntry[] = [];\n\n  for (const chain of allStubs) {\n    for (const stub of chain.stubs) {\n      const file = await fileStorage.getStirlingFile(stub.id);\n      if (!file) {\n        throw new Error(`Missing file data for ${stub.name || stub.id}`);\n      }\n\n      const logicalId = stub.id;\n      const filePath = `files/${logicalId}/${sanitizeFilename(stub.name || \"file\")}`;\n      const buffer = await file.arrayBuffer();\n      zip.file(filePath, buffer);\n\n      entries.push({\n        logicalId,\n        rootLogicalId: chain.rootId,\n        parentLogicalId: stub.parentFileId,\n        versionNumber: stub.versionNumber || 1,\n        name: stub.name,\n        type: stub.type,\n        size: stub.size,\n        lastModified: stub.lastModified,\n        toolHistory: stub.toolHistory,\n        filePath,","sourceCodeStart":47,"sourceCodeEnd":83,"githubUrl":"https://github.com/Stirling-Tools/Stirling-PDF/blob/9ef20dcab80b85041912f045e17a6aea1d08f969/frontend/editor/src/core/services/serverStorageBundle.ts#L47-L83","documentation":"A stub (metadata) exists but `getStirlingFile(stub.id)` returns null — the actual file bytes are gone. `getStirlingFile` returns null when the IndexedDB record is missing, OR when the record is in the session's `unreadableRecords` set (a prior read of a lost WebKit blob-backing-store was detected and the id is short-circuited). So the metadata survived but the blob did not.","triggerScenarios":"WebKit/Safari lost the blob's backing-store data (a known IndexedDB blob bug); the user cleared site data partially; the blob was evicted by an LRU/size policy; the record was marked unreadable earlier in the session after a failed read. Also reachable from `buildSharePackage` at line 129 with the same cause.","commonSituations":"Safari blob-loss after the OS reclaimed disk space; restoring an old IndexedDB whose blobs were garbage-collected; sharing a file whose data was never persisted (memory-only); a prior `getStirlingFile` read failed and added the id to `unreadableRecords`.","solutions":["Before bundling, pre-check `getStirlingFileStub(id).dataUnavailable` (the stub exposes this flag) and exclude/skip unreadable files with a warning instead of aborting the whole bundle.","Offer the user a re-upload flow for the missing file(s).","For Safari, periodically re-hydrate critical blobs (re-write them) to defend against backing-store loss.","Telemetry: log which ids are missing so eviction patterns are visible."],"exampleFix":"// before\nconst file = await fileStorage.getStirlingFile(stub.id);\nif (!file) {\n  throw new Error(`Missing file data for ${stub.name || stub.id}`);\n}\n\n// after (skip missing, continue bundling the rest)\nconst file = await fileStorage.getStirlingFile(stub.id);\nif (!file) {\n  missing.push(stub);\n  continue;\n}\n// after the loop:\nif (missing.length) console.warn('Skipped missing files:', missing.map(s => s.name));\nif (entries.length === 0) throw new Error('All selected files are unavailable.');","handlingStrategy":"validation","validationCode":"// Skip files whose data is flagged unavailable before bundling\nconst stub = await fileStorage.getStirlingFileStub(stubId);\nif (stub?.dataUnavailable) {\n  console.warn(`Skipping unreadable file ${stub.name}`);\n  continue;\n}","typeGuard":"function hasFileData(file: StirlingFile | null): file is StirlingFile {\n  return file !== null && file.size > 0;\n}","tryCatchPattern":"try {\n  await buildHistoryBundle(rootId);\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith(\"Missing file data for\")) {\n    notifyUser(\"Some files in this history are no longer available. Re-upload them to share.\");\n  } else throw e;\n}","preventionTips":["Pre-check stub.dataUnavailable and exclude flagged files before bundling.","Skip missing files and continue bundling the rest rather than aborting the whole bundle.","On Safari, periodically re-hydrate critical blobs to defend against backing-store loss.","Offer a re-upload flow for files whose bytes were evicted."],"tags":["storage","sharing","blob","indexeddb","data-loss","safari"],"backgroundTag":null,"analyzedSha":"9ef20dcab80b85041912f045e17a6aea1d08f969","analyzedAt":"2026-08-13T22:11:39.827Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}