{"record":{"id":"f1b7ae13f34bc7c6","repo":"dani-garcia/vaultwarden","slug":"grantee-user-not-found","errorCode":null,"errorMessage":"Grantee user not found","messagePattern":"Grantee user not found","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/api/core/emergency_access.rs","lineNumber":756,"sourceCode":"            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\");\n                }\n            }\n        }\n    } else {\n        error!(\"Failed to get DB connection while searching emergency request timed out\");\n    }","sourceCodeStart":738,"sourceCodeEnd":774,"githubUrl":"https://github.com/dani-garcia/vaultwarden/blob/0cefa4cca7c9f2a5579dd290f78193b543818c51/src/api/core/emergency_access.rs#L738-L774","documentation":"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.","triggerScenarios":"A recovery timing out for a grantee who deleted their account (or was purged) after acceptance, leaving the emergency_access row intact.","commonSituations":"User churn on instances with long wait_time_days (30-90 days); admin purges; partial database restores.","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"],"exampleFix":"// before\nlet grantee_user = User::find_by_uuid(&emer.grantee_uuid.clone().expect(\"Grantee user invalid\"), &conn).await.expect(\"Grantee user not found\");\n// after\nlet Some(grantee_uuid) = emer.grantee_uuid.clone() else { continue; };\nlet Some(grantee_user) = User::find_by_uuid(&grantee_uuid, &conn).await else {\n    warn!(\"Grantee {grantee_uuid} missing for emergency access {}; skipping\", emer.uuid);\n    continue;\n};","handlingStrategy":"validation","validationCode":"-- Grantee orphans that will panic the job\nSELECT ea.uuid, ea.grantee_uuid FROM emergency_access ea\nLEFT JOIN users u ON u.uuid = ea.grantee_uuid\nWHERE ea.grantee_uuid IS NOT NULL AND u.uuid IS NULL;","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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"],"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"}