dani-garcia/vaultwarden · error
Grantee user not found.
Error message
Grantee user not found.
What it means
Panic from `.expect()` in `EmergencyAccess::to_json_grantee_details` when the record has a grantee_uuid but `User::find_by_uuid` finds no such user. The code guarantees a grantee user for confirmed accesses; an orphaned grantee_uuid (deleted user, stale row) breaks that invariant and panics during JSON serialization.
Solutions
- Find and remove orphaned rows: SELECT * FROM emergency_access WHERE grantee_uuid IS NOT NULL AND grantee_uuid NOT IN (SELECT uuid FROM users); delete them
- Restore the missing grantee user from backup if it should exist
- Check the code path that deletes users and ensure emergency_access rows are cleaned up (cascade delete)
- Change the code to handle a missing grantee gracefully (skip the record or return an error) instead of expect()
Example fix
// before
User::find_by_uuid(grantee_uuid, conn).await.expect("Grantee user not found.")
// after
User::find_by_uuid(grantee_uuid, conn).await.ok_or_else(|| err!("Grantee user not found."))? Defensive patterns
Strategy: validation
Validate before calling
// before serializing, confirm the grantee exists when grantee_uuid is set SELECT 1 FROM users u JOIN emergency_access ea ON ea.grantee_uuid = u.uuid WHERE ea.uuid = ?;
Try / catch
// Skip or error on orphaned grantee rows instead of panicking
match catch_unwind(|| to_json_grantee_details(ea)) { Ok(v) => v, Err(_) => skip_record_with_warning() } Prevention
- Ensure user deletion code removes dependent emergency_access rows
- Run orphaned-grantee consistency checks after partial restores
- Validate invite/confirm flows leave grantee_uuid consistent with status
- Keep DB backups to restore accidentally deleted grantees
When it happens
Trigger: Serializing emergency access where the grantee account was deleted after being confirmed/invited; emergency_access rows with a grantee_uuid pointing to a purged user; partial DB restores leaving dangling references.
Common situations: User self-deletion or admin purge without cascade cleanup of emergency_access; invitation cleanup removed the user but not the link; manual DB manipulation.
Understand the failure class
Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.
Related errors
- Grantor user not found.
- Error loading emergency_access
- Grantee user invalid
- Grantee user not found
- Grantee user should exist but does not!
AI-assisted analysis of dani-garcia/vaultwarden@cc67d644f6 (2026-09-19).
Data as JSON: /api/errors/49be704162f27882.
Report an issue: GitHub.
Appendix: source
Thrown at src/db/models/emergency_access.rs:91
pub async fn to_json_grantor_details(&self, conn: &DbConn) -> Value {
let grantor_user = User::find_by_uuid(&self.grantor_uuid, conn).await.expect("Grantor user not found.");
json!({
"id": self.uuid,
"status": self.status,
"type": self.atype,
"waitTimeDays": self.wait_time_days,
"grantorId": grantor_user.uuid,
"email": grantor_user.email,
"name": grantor_user.name,
"avatarColor": grantor_user.avatar_color,
"object": "emergencyAccessGrantorDetails",
})
}
pub async fn to_json_grantee_details(&self, conn: &DbConn) -> Option<Value> {
let grantee_user = if let Some(grantee_uuid) = &self.grantee_uuid {
User::find_by_uuid(grantee_uuid, conn).await.expect("Grantee user not found.")
} else {
let email = self.email.as_deref()?;
if let Some(user) = User::find_by_mail(email, conn).await {
user
} else {
// remove outstanding invitations which should not exist
Self::delete_all_by_grantee_email(email, conn).await.ok();
return None;
}
};
Some(json!({
"id": self.uuid,
"status": self.status,
"type": self.atype,
"waitTimeDays": self.wait_time_days,
"granteeId": grantee_user.uuid,
"email": grantee_user.email,View on GitHub (pinned to cc67d644f6)