dani-garcia/vaultwarden · error
Grantee user invalid
Error message
Grantee user invalid
What it means
emer.grantee_uuid.clone().expect("Grantee user invalid") in the timeout job assumes every recovery-initiated record carries a grantee uuid. grantee_uuid is only populated when the invite is accepted, and the job's query is meant to return only accepted records — so None here indicates state corruption: status advanced without accept_invite, or manual row edits.
Source
Thrown at src/api/core/emergency_access.rs:754
for mut emer in emergency_access_list {
// The find_all_recoveries_initiated already checks if the recovery_initiated_at is not null (None)
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 {View on GitHub (pinned to 0cefa4cca7)
Solutions
- Inspect and repair the offending rows: set a valid grantee_uuid or reset status/recovery_initiated_at
- Restart the service so the job resumes
- Code fix: skip rows with a missing grantee instead of panicking
Example fix
// before
User::find_by_uuid(&emer.grantee_uuid.clone().expect("Grantee user invalid"), &conn)
// after
let Some(grantee_uuid) = emer.grantee_uuid.clone() else {
warn!("Emergency access {} has no grantee uuid; skipping", emer.uuid);
continue;
};
User::find_by_uuid(&grantee_uuid, &conn) Defensive patterns
Strategy: validation
Validate before calling
-- Rows that will trip the job: recovery initiated but no grantee recorded SELECT uuid, status, grantee_uuid, recovery_initiated_at FROM emergency_access WHERE recovery_initiated_at IS NOT NULL AND grantee_uuid IS NULL;
Prevention
- Never advance emergency_access status via raw SQL
- Test restores against the full schema including dependent rows
- Monitor job panics as an early signal of state corruption
When it happens
Trigger: A row with recovery_initiated_at set but grantee_uuid NULL — usually from direct DB manipulation or a race/bug that advanced the status before acceptance.
Common situations: Hand-edited or partially restored databases; forks altering the emergency access state machine; migration glitches.
Related errors
- Grantee user should exist but does not!
- Unable to update emergency access status
- Grantor user not found
- Grantee user not found
- Unable to update emergency access notification date
AI-assisted analysis of dani-garcia/vaultwarden@0cefa4cca7 (2026-08-16).
Data as JSON: /api/errors/ef450a6da31d9858.
Report an issue: GitHub.