{"record":{"id":"b04a8e20f5294595","repo":"chatboxai/chatbox","slug":"session-is-missing-or-invalid","errorCode":null,"errorMessage":"Session is missing or invalid","messagePattern":"Session is missing or invalid","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"src/renderer/packages/backup/export-backup.ts","lineNumber":259,"sourceCode":"      const metaById = new Map(allMeta.map((meta) => [meta.id, meta]))\n      const sessionIds = unique([\n        ...allMeta.map((meta) => meta.id),\n        ...allStorageKeys.filter((key) => key.startsWith('session:')).map((key) => key.slice('session:'.length)),\n      ])\n\n      for (let index = 0; index < sessionIds.length; index++) {\n        throwIfAborted(options.signal)\n        const sessionId = sessionIds[index]\n        options.onProgress?.({\n          phase: 'sessions',\n          current: index,\n          total: sessionIds.length,\n          label: metaById.get(sessionId)?.name,\n        })\n        try {\n          const session = await options.storage.getItem<Session | null>(backupSessionStorageKey(sessionId), null)\n          if (session === null && !metaById.has(sessionId)) continue\n          if (!isBackupSession(session)) throw new Error('Session is missing or invalid')\n          if (session.id !== sessionId) throw new Error('Session id does not match its storage key')\n          const collected = collectSessionResourceReferences(session)\n          const preparedSession = prepareSessionForBackup(session)\n          const path = `sessions/${await safeSessionPathComponent(session.id)}/session.json`\n          const { archive, descriptor } = await jsonEntry(path, preparedSession)\n          warnings.push(...collected.warnings)\n          addResourceReferences(resourceCandidates, collected.references)\n          sessions.push({\n            entry: {\n              ...descriptor,\n              id: session.id,\n              meta: deriveSessionMeta(preparedSession, metaById.get(session.id)),\n              resourceIds: [],\n            },\n            resourceStorageKeys: new Set(collected.references.map((reference) => reference.storageKey)),\n          })\n          yield archive\n        } catch (error) {","sourceCodeStart":241,"sourceCodeEnd":277,"githubUrl":"https://github.com/chatboxai/chatbox/blob/81571269addb6bafb589a920b2883f1e1e084fd1/src/renderer/packages/backup/export-backup.ts#L241-L277","documentation":"Thrown while iterating session ids during backup export after fetching the Session stored under backupSessionStorageKey(id). It fires when the value is null yet metadata still references that id, or when a non-null value fails the isBackupSession shape check (non-empty string id, string name, array messages). The throw is caught one frame up (lines 277-284) and recorded as a 'session-read-failed' warning, so export continues and the message surfaces in result.manifest.warnings rather than aborting.","triggerScenarios":"A session id appears in metaStorage.getAllIncludingHidden() or as a 'session:' storage key, but the corresponding Session value is absent, partially written, or structurally incomplete (missing/non-string/empty id, missing name, or messages not an array). The guard at line 258 only skips when session===null AND metaById has no entry, so an orphaned meta record falls through to isBackupSession(null) and throws.","commonSituations":"Leftover meta records after a session was deleted without removing its meta row; a crashed prior write that left a half-serialized session; a storage migration that dropped required fields; concurrent edits to sessions during export.","solutions":["Prune orphaned meta records and 'session:' keys before exporting (meta without a valid session value, and session values without meta).","Ensure session writes are atomic so a crash never leaves a malformed Session value in storage.","If the data is genuinely corrupt, delete the offending session value so line 258 skips it cleanly instead of producing a warning.","Set exportItems to exclude 'conversations' to bypass session iteration entirely when only settings/copilots are needed."],"exampleFix":"// before: orphaned meta causes a warning each export\nawait exportBackupArchive(options)\n// after: drop meta rows whose session value is invalid/unreadable\nconst allMeta = await metaStorage.getAllIncludingHidden()\nfor (const meta of allMeta) {\n  const session = await storage.getItem('session:' + meta.id, null)\n  if (!isBackupSession(session)) await metaStorage.delete(meta.id)\n}\nawait exportBackupArchive(options)","handlingStrategy":"validation","validationCode":"// Before export, detect orphaned meta / unreadable sessions so no warning fires.\nconst allMeta = await metaStorage.getAllIncludingHidden()\nconst orphans: string[] = []\nfor (const meta of allMeta) {\n  const session = await storage.getItem('session:' + meta.id, null)\n  if (!isBackupSession(session)) orphans.push(meta.id)\n}\nif (orphans.length) console.warn('Orphaned sessions will be skipped:', orphans)","typeGuard":"import { isBackupSession } from './archive-layout'\n// Returns true only when value is an object with non-empty string id,\n// string name, and array messages - the exact shape this error checks.\nconst sessionIsExportable = (v: unknown): v is Session => isBackupSession(v)","tryCatchPattern":null,"preventionTips":["Delete session meta and the 'session:' key together (atomically) so one never outlives the other.","After any failed session write, run a cleanup that removes half-written session values.","Inspect result.manifest.warnings after every export and treat 'session-read-failed' entries as data-hygiene debt."],"tags":["backup","export","session","data-integrity","orphaned-reference"],"backgroundTag":null,"analyzedSha":"81571269addb6bafb589a920b2883f1e1e084fd1","analyzedAt":"2026-08-12T21:51:44.981Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}