chatboxai/chatbox · warning · Error

Only failed session attachments can be retried

Error message

Only failed session attachments can be retried

What it means

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.

Source

Thrown at src/main/session-attachment-rag/db.ts:671

export async function markSessionAttachmentFailed(id: number, error: string) {
  const client = getDatabase()
  const result = await client.execute({
    sql: 'UPDATE session_attachment SET status = ?, error = ?, processing_started_at = NULL, completed_at = NULL WHERE id = ? AND status != ?',
    args: ['failed', error, id, 'canceled'],
  })
  if ((result.rowsAffected || 0) > 0) {
    log.debug(`${SESSION_ATTACHMENT_RAG_LOG_PREFIX} [DB] Marked attachment failed: attachmentId=${id}, error=${error}`)
  }
}

export async function retrySessionAttachment(id: number) {
  const client = getDatabase()
  const existing = await getSessionAttachment(id)
  if (!existing) {
    throw new Error(`Session attachment ${id} not found`)
  }
  if (existing.status !== 'failed') {
    throw new Error('Only failed session attachments can be retried')
  }
  await client.execute({
    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 = ?',
    args: ['pending', 'queued', id],
  })
  log.debug(`${SESSION_ATTACHMENT_RAG_LOG_PREFIX} [DB] Reset attachment to pending: attachmentId=${id}`)
}

export async function cancelSessionAttachment(id: number) {
  const client = getDatabase()
  await client.execute({
    sql: 'UPDATE session_attachment SET status = ?, error = NULL, processing_started_at = NULL, completed_at = NULL WHERE id = ? AND status IN (?, ?, ?)',
    args: ['canceled', id, 'pending', 'indexing', 'failed'],
  })
  log.debug(`${SESSION_ATTACHMENT_RAG_LOG_PREFIX} [DB] Marked attachment canceled: attachmentId=${id}`)
}

export async function rebindSessionAttachment(id: number, sessionId: string, messageId: string) {

View on GitHub (pinned to 81571269ad)

Solutions

  1. Gate the retry button in the UI on status === 'failed' only.
  2. If the status changed between render and click, swallow this error and refresh the row from the DB.
  3. For canceled attachments, offer 're-upload' instead of 'retry' since retry is intentionally blocked.
  4. Re-read status immediately before calling retry if the list is long-lived.

Example fix

// before
if (existing.status !== 'failed') throw new Error('Only failed session attachments can be retried')

// caller guard
if (attachment.status === 'failed') await retrySessionAttachment(attachment.id)
Defensive patterns

Strategy: validation

Validate before calling

const a = await getSessionAttachment(id)
if (!a || a.status !== 'failed') { return }

Type guard

function isRetryStatusBlocked(e: unknown): e is Error { return e instanceof Error && e.message === 'Only failed session attachments can be retried' }

Try / catch

try { await retrySessionAttachment(id) } catch (e) { if (isRetryStatusBlocked(e)) { ui.refreshRow(id); return } throw e }

Prevention

When it happens

Trigger: Calling retry on an attachment currently in 'processing' or 'queued', or one already 'ready'/'canceled'. The check is strict equality status !== 'failed'.

Common situations: 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).

Related errors


AI-assisted analysis of chatboxai/chatbox@81571269ad (2026-08-12). Data as JSON: /api/errors/3d265cfdf54d582f. Report an issue: GitHub.