dani-garcia/vaultwarden · error
Grantee user not found
Error message
Grantee user not found
What it means
The grantee lookup in the timeout job, User::find_by_uuid(...).await.expect("Grantee user not found"), panics the job task when grantee_uuid points at a deleted user — mirroring the grantor case. The panic halts the job for all remaining rows even though only one row is broken.
Source
Thrown at src/api/core/emergency_access.rs:756
let recovery_allowed_at =
emer.recovery_initiated_at.unwrap() + TimeDelta::try_days(i64::from(emer.wait_time_days)).unwrap();
if recovery_allowed_at.le(&now) {
// Only update the access status
// Updating the whole record could cause issues when the emergency_notification_reminder_job is also active
emer.update_access_status_and_save(EmergencyAccessStatus::RecoveryApproved as i32, &now, &conn)
.await
.expect("Unable to update emergency access status");
if CONFIG.mail_enabled() {
// get grantor user to send Accepted email
let grantor_user =
User::find_by_uuid(&emer.grantor_uuid, &conn).await.expect("Grantor user not found");
// get grantee user to send Accepted email
let grantee_user =
User::find_by_uuid(&emer.grantee_uuid.clone().expect("Grantee user invalid"), &conn)
.await
.expect("Grantee user not found");
mail::send_emergency_access_recovery_timed_out(
&grantor_user.email,
&grantee_user.name,
emer.get_type_as_str(),
)
.await
.expect("Error on sending email");
mail::send_emergency_access_recovery_approved(&grantee_user.email, &grantor_user.name)
.await
.expect("Error on sending email");
}
}
}
} else {
error!("Failed to get DB connection while searching emergency request timed out");
}View on GitHub (pinned to 0cefa4cca7)
Solutions
- Clean up orphans (back up first): DELETE FROM emergency_access WHERE grantee_uuid IS NOT NULL AND grantee_uuid NOT IN (SELECT uuid FROM users)
- Restart Vaultwarden to revive the job task
- Code fix: skip rows whose users are missing
Example fix
// before
let grantee_user = User::find_by_uuid(&emer.grantee_uuid.clone().expect("Grantee user invalid"), &conn).await.expect("Grantee user not found");
// after
let Some(grantee_uuid) = emer.grantee_uuid.clone() else { continue; };
let Some(grantee_user) = User::find_by_uuid(&grantee_uuid, &conn).await else {
warn!("Grantee {grantee_uuid} missing for emergency access {}; skipping", emer.uuid);
continue;
}; Defensive patterns
Strategy: validation
Validate before calling
-- Grantee orphans that will panic the job SELECT ea.uuid, ea.grantee_uuid FROM emergency_access ea LEFT JOIN users u ON u.uuid = ea.grantee_uuid WHERE ea.grantee_uuid IS NOT NULL AND u.uuid IS NULL;
Prevention
- When deleting users, also remove emergency_access rows referencing them
- Periodically run the orphan query on long-lived instances
- Restart after cleanup so panicked job tasks come back
When it happens
Trigger: A recovery timing out for a grantee who deleted their account (or was purged) after acceptance, leaving the emergency_access row intact.
Common situations: User churn on instances with long wait_time_days (30-90 days); admin purges; partial database restores.
Related errors
- Grantee user should exist but does not!
- Grantor user not found
- Unable to update emergency access status
- Grantee user invalid
- Unable to update emergency access notification date
AI-assisted analysis of dani-garcia/vaultwarden@0cefa4cca7 (2026-08-16).
Data as JSON: /api/errors/f1b7ae13f34bc7c6.
Report an issue: GitHub.