{"record":{"id":"08d3d93aec07ea57","repo":"paperclipai/paperclip","slug":"saved-channel-delivery-identity-could-not-be-read-check","errorCode":null,"errorMessage":"Saved channel delivery identity could not be read. Check Activity before starting another send.","messagePattern":"Saved channel delivery identity could not be read\\. Check Activity before starting another send\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"ui/src/components/chat/board-send-draft.ts","lineNumber":117,"sourceCode":"        value.attachmentNames.length !== value.attachmentIds.length ||\n        new Set(value.attachmentNames.map((file) => file?.id)).size !==\n          value.attachmentNames.length ||\n        !value.attachmentNames.every(\n          (file) =>\n            file &&\n            typeof file.id === \"string\" &&\n            value.attachmentIds!.includes(file.id) &&\n            typeof file.name === \"string\",\n        ))) ||\n    (value.publication !== null &&\n      (!value.publication ||\n        typeof value.publication.id !== \"string\" ||\n        !value.publication.id ||\n        !CHAT_PUBLICATION_STATES.includes(value.publication.state) ||\n        !Number.isSafeInteger(value.publication.attempts) ||\n        value.publication.attempts < 0))\n  )\n    throw new Error(\n      \"Saved channel delivery identity could not be read. Check Activity before starting another send.\",\n    );\n  return value as RetainedBoardSend;\n}\n\nexport function writeBoardSendDraft(key: string, value: RetainedBoardSend) {\n  // This is a delivery identity, not a best-effort text draft. The caller must\n  // stop before POST when storage fails; otherwise reload could create a duplicate.\n  sessionStorage.setItem(key, JSON.stringify(value));\n}\n\nexport function clearBoardSendDraft(key: string) {\n  sessionStorage.removeItem(key);\n}\n\n/** A terminal selected row alone cannot release a still-running batch. */\nexport function canDismissBoardSendBatch(\n  batch: ChatPublicationBatchStatus | undefined,","sourceCodeStart":99,"sourceCodeEnd":135,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/ui/src/components/chat/board-send-draft.ts#L99-L135","documentation":"`readBoardSendDraft` parses a retained board-send draft from storage and validates its shape (recipient, string publication id, known publication state, non-negative integer attempts). If the stored JSON parses but fails this schema validation, it throws this error because the saved delivery identity is corrupt and reusing it could duplicate or misattribute a channel send.","triggerScenarios":"Reading a draft persisted by an older app version with a different schema; manual tampering with localStorage; a truncated/partial write from a previous crashed write.","commonSituations":"App upgrade changed RetainedBoardSend shape (e.g. publication fields added); user cleared/edited localStorage; storage quota caused a truncated write previously.","solutions":["Clear the offending localStorage key (or use a versioned key bump) so a fresh draft is written on next send.","Wrap `readBoardSendDraft`/`saved` in try-catch, discard the invalid draft, and prompt the user to check Activity before resending.","Add a schema version field to stored drafts and migrate or invalidate on mismatch."],"exampleFix":"// before\nconst draft = saved(key);\n// after\nlet draft;\ntry { draft = saved(key); }\ncatch { localStorage.removeItem(key); draft = null; /* surface 'check Activity' hint */ }","handlingStrategy":"try-catch","validationCode":"function isRetainedBoardSend(v: unknown): boolean { const o = v as any; return !!o && typeof o.to === 'string' && typeof o.publication?.id === 'string' && Number.isSafeInteger(o.publication?.attempts); }","typeGuard":"const isRetained = (v: unknown): v is RetainedBoardSend => { const o = v as RetainedBoardSend; return !!o && typeof o.to === 'string' && !!o.publication && typeof o.publication.id === 'string' && CHAT_PUBLICATION_STATES.includes(o.publication.state); };","tryCatchPattern":"let draft: RetainedBoardSend | null = null; try { draft = readBoardSendDraft(key); } catch { localStorage.removeItem(key); promptCheckActivity(); }","preventionTips":["Version stored drafts and invalidate unknown versions.","Wrap all reads of persisted send state in try-catch.","Use atomic JSON writes (serialize then set) to avoid truncated values."],"tags":["localstorage","schema","validation"],"backgroundTag":"schema-validation-failed","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}