{"record":{"id":"93d0f37363414779","repo":"dani-garcia/vaultwarden","slug":"unable-to-update-emergency-access-status","errorCode":null,"errorMessage":"Unable to update emergency access status","messagePattern":"Unable to update emergency access status","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/api/core/emergency_access.rs","lineNumber":745,"sourceCode":"\n    if let Ok(conn) = pool.get().await {\n        let emergency_access_list = EmergencyAccess::find_all_recoveries_initiated(&conn).await;\n\n        if emergency_access_list.is_empty() {\n            debug!(\"No emergency request timeout to approve\");\n        }\n\n        let now = Utc::now().naive_utc();\n        for mut emer in emergency_access_list {\n            // The find_all_recoveries_initiated already checks if the recovery_initiated_at is not null (None)\n            let recovery_allowed_at =\n                emer.recovery_initiated_at.unwrap() + TimeDelta::try_days(i64::from(emer.wait_time_days)).unwrap();\n            if recovery_allowed_at.le(&now) {\n                // Only update the access status\n                // Updating the whole record could cause issues when the emergency_notification_reminder_job is also active\n                emer.update_access_status_and_save(EmergencyAccessStatus::RecoveryApproved as i32, &now, &conn)\n                    .await\n                    .expect(\"Unable to update emergency access status\");\n\n                if CONFIG.mail_enabled() {\n                    // get grantor user to send Accepted email\n                    let grantor_user =\n                        User::find_by_uuid(&emer.grantor_uuid, &conn).await.expect(\"Grantor user not found\");\n\n                    // get grantee user to send Accepted email\n                    let grantee_user =\n                        User::find_by_uuid(&emer.grantee_uuid.clone().expect(\"Grantee user invalid\"), &conn)\n                            .await\n                            .expect(\"Grantee user not found\");\n\n                    mail::send_emergency_access_recovery_timed_out(\n                        &grantor_user.email,\n                        &grantee_user.name,\n                        emer.get_type_as_str(),\n                    )\n                    .await","sourceCodeStart":727,"sourceCodeEnd":763,"githubUrl":"https://github.com/dani-garcia/vaultwarden/blob/0cefa4cca7c9f2a5579dd290f78193b543818c51/src/api/core/emergency_access.rs#L727-L763","documentation":"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.","triggerScenarios":"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).","commonSituations":"DB restart at job time; SQLite lock contention; two jobs touching the same emergency access row; long-running transactions elsewhere.","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"],"exampleFix":"// before\nemer.update_access_status_and_save(EmergencyAccessStatus::RecoveryApproved as i32, &now, &conn)\n    .await\n    .expect(\"Unable to update emergency access status\");\n// after\nif let Err(e) = emer.update_access_status_and_save(EmergencyAccessStatus::RecoveryApproved as i32, &now, &conn).await {\n    error!(\"Failed to update emergency access {}: {e}\", emer.uuid);\n    continue;\n}","handlingStrategy":"try-catch","validationCode":"-- Preview recovery rows the timeout job will touch and flag ones with missing users\nSELECT ea.uuid, ea.grantor_uuid, ea.grantee_uuid, ea.wait_time_days, ea.recovery_initiated_at\nFROM emergency_access ea\nWHERE ea.recovery_initiated_at IS NOT NULL\n  AND NOT EXISTS (\n    SELECT 1 FROM users u\n    WHERE u.uuid = ea.grantor_uuid OR u.uuid = ea.grantee_uuid\n  );","typeGuard":null,"tryCatchPattern":"// Per-row resilient pattern for scheduled jobs\nfor emer in emergency_access_list {\n    if let Err(e) = emer.update_access_status_and_save(status, &now, &conn).await {\n        error!(\"emergency timeout job: skip {}: {e}\", emer.uuid);\n        continue;\n    }\n    // notify users ...\n}","preventionTips":["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"],"tags":["rust","emergency-access","database","panic","background-jobs"],"backgroundTag":null,"analyzedSha":"0cefa4cca7c9f2a5579dd290f78193b543818c51","analyzedAt":"2026-08-16T07:44:56.102Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}