{"record":{"id":"ca732e9db2ab2b2f","repo":"moeru-ai/airi","slug":"chat-session-persist-task-failed","errorCode":null,"errorMessage":"[chat-session] persist task failed:","messagePattern":"\\[chat-session\\] persist task failed:","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/stage-ui/src/stores/chat/session-store.ts","lineNumber":167,"sourceCode":"    return cloudMapper\n  }\n\n  /**\n   * Append a write task to the persist queue. Tasks always run sequentially\n   * regardless of whether prior tasks rejected — but rejections propagate to\n   * the awaiting caller AND are surfaced via console for debugging. The\n   * previous `then(task, task)` form silently swallowed prior rejections by\n   * running the next task as the rejection handler, which masked IDB\n   * failures from the cloud-sync cursor tracking that depends on them.\n   */\n  function enqueuePersist<T>(task: () => Promise<T>): Promise<T> {\n    const next = persistQueue.then(task)\n    // Keep the queue alive after a rejection but log it so silent IDB\n    // failures (quota, corruption) surface during dev.\n    persistQueue = next.then(\n      () => undefined,\n      (err) => {\n        console.warn('[chat-session] persist task failed:', errorMessageFrom(err))\n      },\n    )\n    return next\n  }\n\n  function snapshotMessages(messages: ChatHistoryItem[]) {\n    return cloneDeep(messages)\n  }\n\n  function ensureSessionMessageIds(sessionId: string) {\n    const current = sessionMessages.value[sessionId] ?? []\n    let changed = false\n    const next = current.map((message) => {\n      if (message.id)\n        return message\n      changed = true\n      return {\n        ...message,","sourceCodeStart":149,"sourceCodeEnd":185,"githubUrl":"https://github.com/moeru-ai/airi/blob/f679616c34f1cf6d282c8d64264242af944b3fed/packages/stage-ui/src/stores/chat/session-store.ts#L149-L185","documentation":"session-store serializes persistence through a promise chain (enqueuePersist). Each task's rejection is logged here and swallowed so the chain survives for later writes. Failures originate in the IndexedDB layer behind the persist tasks: quota exceeded, database closed/version change mid-write, corruption, or an abort during tab shutdown. Because tasks return the original promise to awaiting callers, callers may also observe the rejection; this log is the queue-level record.","triggerScenarios":"Any IDB write inside a persist task rejects — large chat histories hitting storage quota, IDB version upgrade closing connections, private-mode/restricted storage (ITP evicting or blocking), or structured-clone failure on non-cloneable message payloads.","commonSituations":"Long-lived chats accumulating megabytes of history in Safari/WebKit quota; browser data-clearing while the app is open; Firefox private browsing where IDB writes fail; storing non-cloneable values (functions, DOM nodes) accidentally attached to messages.","solutions":["Check the logged message for QuotaExceededError — prune or export old chat history, and consider trimming snapshot payloads.","Handle versionchange/close events in the IDB wrapper so writes are not attempted on a dead connection.","Ensure message objects are structured-clone-safe (no functions/DOM refs) before persisting.","If in private/restricted mode, warn users that chat persistence is unavailable."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"function isStructuredCloneSafe(value: unknown): boolean {\n  try {\n    structuredClone(value)\n    return true\n  }\n  catch {\n    return false\n  }\n}\n// validate message payloads before enqueueing persist tasks","typeGuard":"function isQuotaError(err: unknown): boolean {\n  const name = (err as { name?: string })?.name\n  return name === 'QuotaExceededError' || errorMessageFrom(err).includes('quota')\n}","tryCatchPattern":"function enqueuePersist<T>(task: () => Promise<T>): Promise<T> {\n  const next = persistQueue.then(task)\n  persistQueue = next.then(\n    () => undefined,\n    (err) => {\n      if (isQuotaError(err)) {\n        // prune history / notify user instead of silently retrying forever\n      }\n      console.warn('[chat-session] persist task failed:', errorMessageFrom(err))\n    },\n  )\n  return next\n}","preventionTips":["Keep persisted message snapshots lean; strip volatile/DOM references before storing.","Verify structured-clone safety of new message fields before persisting them.","Surface quota errors to the UI so users can export/prune history."],"tags":["indexeddb","persistence","quota","chat-session"],"backgroundTag":"indexeddb-write-failed","analyzedSha":"f679616c34f1cf6d282c8d64264242af944b3fed","analyzedAt":"2026-08-18T17:29:58.153Z","contentChangedAt":"2026-08-18T17:29:58.153Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}