{"record":{"id":"3d265cfdf54d582f","repo":"chatboxai/chatbox","slug":"only-failed-session-attachments-can-be-retried","errorCode":null,"errorMessage":"Only failed session attachments can be retried","messagePattern":"Only failed session attachments can be retried","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"src/main/session-attachment-rag/db.ts","lineNumber":671,"sourceCode":"export async function markSessionAttachmentFailed(id: number, error: string) {\n  const client = getDatabase()\n  const result = await client.execute({\n    sql: 'UPDATE session_attachment SET status = ?, error = ?, processing_started_at = NULL, completed_at = NULL WHERE id = ? AND status != ?',\n    args: ['failed', error, id, 'canceled'],\n  })\n  if ((result.rowsAffected || 0) > 0) {\n    log.debug(`${SESSION_ATTACHMENT_RAG_LOG_PREFIX} [DB] Marked attachment failed: attachmentId=${id}, error=${error}`)\n  }\n}\n\nexport async function retrySessionAttachment(id: number) {\n  const client = getDatabase()\n  const existing = await getSessionAttachment(id)\n  if (!existing) {\n    throw new Error(`Session attachment ${id} not found`)\n  }\n  if (existing.status !== 'failed') {\n    throw new Error('Only failed session attachments can be retried')\n  }\n  await client.execute({\n    sql: 'UPDATE session_attachment SET status = ?, indexing_stage = ?, total_chunks = 0, embedded_chunks = 0, error = NULL, processing_started_at = NULL, completed_at = NULL WHERE id = ?',\n    args: ['pending', 'queued', id],\n  })\n  log.debug(`${SESSION_ATTACHMENT_RAG_LOG_PREFIX} [DB] Reset attachment to pending: attachmentId=${id}`)\n}\n\nexport async function cancelSessionAttachment(id: number) {\n  const client = getDatabase()\n  await client.execute({\n    sql: 'UPDATE session_attachment SET status = ?, error = NULL, processing_started_at = NULL, completed_at = NULL WHERE id = ? AND status IN (?, ?, ?)',\n    args: ['canceled', id, 'pending', 'indexing', 'failed'],\n  })\n  log.debug(`${SESSION_ATTACHMENT_RAG_LOG_PREFIX} [DB] Marked attachment canceled: attachmentId=${id}`)\n}\n\nexport async function rebindSessionAttachment(id: number, sessionId: string, messageId: string) {","sourceCodeStart":653,"sourceCodeEnd":689,"githubUrl":"https://github.com/chatboxai/chatbox/blob/81571269addb6bafb589a920b2883f1e1e084fd1/src/main/session-attachment-rag/db.ts#L653-L689","documentation":"Thrown by retrySessionAttachment when the attachment exists but its status is not 'failed'. Only failed attachments are eligible for retry; pending/queued/processing/ready/canceled are rejected to avoid conflicting with an in-flight pipeline or resurrecting a clean state.","triggerScenarios":"Calling retry on an attachment currently in 'processing' or 'queued', or one already 'ready'/'canceled'. The check is strict equality status !== 'failed'.","commonSituations":"UI shows a retry button for all rows regardless of status; double-click retry on a row that just transitioned from failed to pending; retrying a canceled attachment (cancellation is terminal by intent).","solutions":["Gate the retry button in the UI on status === 'failed' only.","If the status changed between render and click, swallow this error and refresh the row from the DB.","For canceled attachments, offer 're-upload' instead of 'retry' since retry is intentionally blocked.","Re-read status immediately before calling retry if the list is long-lived."],"exampleFix":"// before\nif (existing.status !== 'failed') throw new Error('Only failed session attachments can be retried')\n\n// caller guard\nif (attachment.status === 'failed') await retrySessionAttachment(attachment.id)","handlingStrategy":"validation","validationCode":"const a = await getSessionAttachment(id)\nif (!a || a.status !== 'failed') { return }","typeGuard":"function isRetryStatusBlocked(e: unknown): e is Error { return e instanceof Error && e.message === 'Only failed session attachments can be retried' }","tryCatchPattern":"try { await retrySessionAttachment(id) } catch (e) { if (isRetryStatusBlocked(e)) { ui.refreshRow(id); return } throw e }","preventionTips":["Show retry only for status==='failed'.","Re-read status right before retrying in long-lived lists.","Use re-upload (not retry) for canceled attachments."],"tags":["session-attachment-rag","database","state-machine","validation","typescript"],"backgroundTag":null,"analyzedSha":"81571269addb6bafb589a920b2883f1e1e084fd1","analyzedAt":"2026-08-12T21:51:44.981Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}