dani-garcia/vaultwarden · error
Unable to update emergency access status
Error message
Unable to update emergency access status
What it means
emergency_request_timeout_job (periodic background job): for each recovery whose wait_time_days elapsed past recovery_initiated_at, it flips status to RecoveryApproved via update_access_status_and_save(...).expect("Unable to update emergency access status"). A DB error panics the tokio task running the job, halting that job until the process restarts; the panic is logged.
Source
Thrown at src/api/core/emergency_access.rs:745
if let Ok(conn) = pool.get().await {
let emergency_access_list = EmergencyAccess::find_all_recoveries_initiated(&conn).await;
if emergency_access_list.is_empty() {
debug!("No emergency request timeout to approve");
}
let now = Utc::now().naive_utc();
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(),
)
.awaitView on GitHub (pinned to 0cefa4cca7)
Solutions
- Restore DB availability and restart Vaultwarden so the scheduler task is recreated
- Reduce contention: WAL for SQLite, avoid manual edits while jobs run
- Verify affected rows afterwards: statuses should reflect the timeouts that were missed
- Code fix: log and continue per row instead of expect
Example fix
// before
emer.update_access_status_and_save(EmergencyAccessStatus::RecoveryApproved as i32, &now, &conn)
.await
.expect("Unable to update emergency access status");
// after
if let Err(e) = emer.update_access_status_and_save(EmergencyAccessStatus::RecoveryApproved as i32, &now, &conn).await {
error!("Failed to update emergency access {}: {e}", emer.uuid);
continue;
} Defensive patterns
Strategy: try-catch
Validate before calling
-- Preview recovery rows the timeout job will touch and flag ones with missing users
SELECT ea.uuid, ea.grantor_uuid, ea.grantee_uuid, ea.wait_time_days, ea.recovery_initiated_at
FROM emergency_access ea
WHERE ea.recovery_initiated_at IS NOT NULL
AND NOT EXISTS (
SELECT 1 FROM users u
WHERE u.uuid = ea.grantor_uuid OR u.uuid = ea.grantee_uuid
); Try / catch
// Per-row resilient pattern for scheduled jobs
for emer in emergency_access_list {
if let Err(e) = emer.update_access_status_and_save(status, &now, &conn).await {
error!("emergency timeout job: skip {}: {e}", emer.uuid);
continue;
}
// notify users ...
} Prevention
- Keep the DB reachable at job intervals
- Clean orphaned emergency_access rows before enabling jobs
- Monitor panic logs: a single panic halts that job until restart
When it happens
Trigger: The scheduled job firing while the DB is unavailable or locked, or the row being deleted/modified between the SELECT and the UPDATE (race with the reminder job or manual edits).
Common situations: DB restart at job time; SQLite lock contention; two jobs touching the same emergency access row; long-running transactions elsewhere.
Related errors
- Unable to update emergency access notification date
- Grantee user should exist but does not!
- Grantor user not found
- Grantee user invalid
- Grantee user not found
AI-assisted analysis of dani-garcia/vaultwarden@0cefa4cca7 (2026-08-16).
Data as JSON: /api/errors/93d0f37363414779.
Report an issue: GitHub.