dani-garcia/vaultwarden · error
Error updating attachment
Error message
Error updating attachment
What it means
Completion of a direct attachment upload (POST /ciphers/{cipher_uuid}/attachments/{attachment_id}): when the streamed size differs from the declared size but stays within the ±1MiB leeway, the server rewrites attachment.file_size with .expect("Error updating attachment") — a DB failure here panics after the file bytes were already stored, leaving metadata stale or the row missing.
Source
Thrown at src/api/core/ciphers.rs:1298
if let Some(attachment) = &mut attachment {
// v2 API
// Check the actual size against the size initially provided by
// the client. Upstream allows +/- 1 MiB deviation from this
// size, but it's not clear when or why this is needed.
const LEEWAY: i64 = 1024 * 1024; // 1 MiB
let Some(max_size) = attachment.file_size.checked_add(LEEWAY) else {
err!("Invalid attachment size max")
};
let Some(min_size) = attachment.file_size.checked_sub(LEEWAY) else {
err!("Invalid attachment size min")
};
if min_size <= size && size <= max_size {
if size != attachment.file_size {
// Update the attachment with the actual file size.
attachment.file_size = size;
attachment.save(&conn).await.expect("Error updating attachment");
}
} else {
attachment.delete(&conn).await.ok();
err!(format!("Attachment size mismatch (expected within [{min_size}, {max_size}], got {size})"));
}
} else {
// Legacy API
// SAFETY: This value is only stored in the database and is not used to access the file system.
// As a result, the conditions specified by Rocket [0] are met and this is safe to use.
// [0]: https://docs.rs/rocket/latest/rocket/fs/struct.FileName.html#-danger-
let encrypted_filename = data.data.raw_name().map(|s| s.dangerous_unsafe_unsanitized_raw().to_string());
if encrypted_filename.is_none() {
err!("No filename provided")
}
if data.key.is_none() {View on GitHub (pinned to 0cefa4cca7)
Solutions
- Retry the upload from the start; remove the orphaned attachment record on the cipher if needed
- Restore DB availability; confirm the attachments row still exists before retrying
- Code fix: return a proper error instead of expect
Example fix
// before
attachment.save(&conn).await.expect("Error updating attachment");
// after
attachment.save(&conn).await.map_err(|e| Error::new("Failed to update attachment size", e.to_string()))?; Defensive patterns
Strategy: try-catch
Validate before calling
-- Verify the pending attachment row still exists before finishing the upload SELECT id, file_size, akey FROM attachments WHERE id = '<attachment_id>';
Try / catch
match attachment.save(&conn).await {
Ok(_) => {}
Err(e) => {
error!("Failed to update attachment size for {}: {e}", attachment.id);
return Err(Error::new("Failed to update attachment size", e.to_string()));
}
} Prevention
- Declare accurate file sizes when starting v2 uploads
- Do not delete ciphers/attachments while uploads are in flight
- Keep the DB healthy during upload completion windows
When it happens
Trigger: Finishing an upload where actual size != declared size (within 1MiB) and the update fails: locked DB, the row concurrently deleted, or connection loss at completion time.
Common situations: Flaky networks ending uploads with slightly different sizes; two clients finishing the same attachment id; DB maintenance exactly when uploads complete.
Related errors
- Error saving attachment
- Error saving API key
- Grantee user should exist but does not!
- Unable to update emergency access status
- Grantor user not found
AI-assisted analysis of dani-garcia/vaultwarden@0cefa4cca7 (2026-08-16).
Data as JSON: /api/errors/aa17b9c438abb38f.
Report an issue: GitHub.