{"record":{"id":"110167e934f7c5c8","repo":"dani-garcia/vaultwarden","slug":"grantor-user-not-found","errorCode":null,"errorMessage":"Grantor user not found","messagePattern":"Grantor user not found","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/api/core/emergency_access.rs","lineNumber":750,"sourceCode":"            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\n                    .expect(\"Error on sending email\");\n\n                    mail::send_emergency_access_recovery_approved(&grantee_user.email, &grantor_user.name)\n                        .await\n                        .expect(\"Error on sending email\");","sourceCodeStart":732,"sourceCodeEnd":768,"githubUrl":"https://github.com/dani-garcia/vaultwarden/blob/0cefa4cca7c9f2a5579dd290f78193b543818c51/src/api/core/emergency_access.rs#L732-L768","documentation":"In the same timeout job with mail enabled, the grantor user is loaded with User::find_by_uuid(&emer.grantor_uuid, &conn).await.expect(\"Grantor user not found\") to address the notification email. If the grantor account was deleted while the emergency_access row survived, the lookup returns None and the job task panics, stopping the job for all remaining rows until restart.","triggerScenarios":"A recovery timing out for an emergency access whose grantor_uuid no longer matches a user row — deletion without cleanup, corrupted uuid values, or partial restores.","commonSituations":"Admin-deleted or purged accounts with lingering emergency access; database restores that dropped users but kept emergency_access rows.","solutions":["Purge or fix orphaned rows (back up first): DELETE FROM emergency_access WHERE grantor_uuid NOT IN (SELECT uuid FROM users)","Restart Vaultwarden to restart the job task","Code fix: treat a missing user as a skip, not a panic"],"exampleFix":"// before\nlet grantor_user = User::find_by_uuid(&emer.grantor_uuid, &conn).await.expect(\"Grantor user not found\");\n// after\nlet Some(grantor_user) = User::find_by_uuid(&emer.grantor_uuid, &conn).await else {\n    warn!(\"Grantor {} missing for emergency access {}; skipping\", emer.grantor_uuid, emer.uuid);\n    continue;\n};","handlingStrategy":"validation","validationCode":"-- Detect grantor orphans before the job does\nSELECT ea.uuid, ea.grantor_uuid FROM emergency_access ea\nLEFT JOIN users u ON u.uuid = ea.grantor_uuid\nWHERE u.uuid IS NULL;","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Delete emergency accesses before deleting the owning user","Run orphan checks after user purges or merges","Back up before DELETE queries against emergency_access"],"tags":["rust","emergency-access","database","panic","data-integrity"],"backgroundTag":null,"analyzedSha":"0cefa4cca7c9f2a5579dd290f78193b543818c51","analyzedAt":"2026-08-16T07:44:56.102Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}