dani-garcia/vaultwarden · error

Error saving attachment

Error message

Error saving attachment

What it means

Start of the v2 attachment flow (POST /ciphers/{uuid}/attachment/v2): after write-access checks and file_size parsing, a new Attachment row is inserted with attachment.save(&conn).await.expect("Error saving attachment") — any DB failure panics the request before file bytes are transferred, so the client can simply retry once the DB is healthy.

Solutions

  1. Restore DB health and retry the upload
  2. Enable WAL / reduce concurrent writers for SQLite
  3. Apply migrations and verify the attachments table schema
  4. Code fix: propagate the save error instead of expect

Example fix

// before
attachment.save(&conn).await.expect("Error saving attachment");
// after
attachment.save(&conn).await.map_err(|e| Error::new("Failed to save attachment", e.to_string()))?;
Defensive patterns

Strategy: try-catch

Validate before calling

# Confirm the attachments table is reachable before mass uploads
sqlite3 /data/db.sqlite3 "SELECT count(*) FROM attachments;" && echo attachments-table-ok

Try / catch

match attachment.save(&conn).await {
    Ok(_) => {}
    Err(e) => {
        error!("Failed to save attachment {}: {e}", attachment.id);
        return Err(Error::new("Failed to save attachment", e.to_string()));
    }
}

Prevention

When it happens

Trigger: Starting an attachment upload from the web vault/CLI when the attachment insert fails: locked SQLite, dead connection, constraint violation, or full disk.

Common situations: Concurrent uploads on SQLite without WAL; DB failover mid-session; migrations not applied after an upgrade; batch imports creating many attachments at once.

Related errors


AI-assisted analysis of dani-garcia/vaultwarden@6729e83521 (2026-08-16). Data as JSON: /api/errors/752e522692af6b93. Report an issue: GitHub.

Appendix: source

Thrown at src/api/core/ciphers.rs:1150

) -> JsonResult {
    let Some(cipher) = Cipher::find_by_uuid(&cipher_id, &conn).await else {
        err!("Cipher doesn't exist")
    };

    if !cipher.is_write_accessible_to_user(&headers.user.uuid, &conn).await {
        err!("Cipher is not write accessible")
    }

    let data: AttachmentRequestData = data.into_inner();
    let file_size = data.file_size.into_i64()?;

    if file_size < 0 {
        err!("Attachment size can't be negative")
    }
    let attachment_id = crypto::generate_attachment_id();
    let attachment =
        Attachment::new(attachment_id.clone(), cipher.uuid.clone(), data.file_name, file_size, Some(data.key));
    attachment.save(&conn).await.expect("Error saving attachment");

    let url = format!("/ciphers/{}/attachment/{attachment_id}", cipher.uuid);
    let response_key = match data.admin_request {
        Some(b) if b => "cipherMiniResponse",
        _ => "cipherResponse",
    };

    Ok(Json(json!({ // AttachmentUploadDataResponseModel
        "object": "attachment-fileUpload",
        "attachmentId": attachment_id,
        "url": url,
        "fileUploadType": FileUploadType::Direct as i32,
        response_key: cipher.to_json(&headers.host, &headers.user.uuid, None, CipherSyncType::User, &conn).await?,
    })))
}

#[derive(FromForm)]
struct UploadData<'f> {

View on GitHub (pinned to 6729e83521)