{"record":{"id":"49be704162f27882","repo":"dani-garcia/vaultwarden","slug":"grantee-user-not-found-emergency-access","errorCode":null,"errorMessage":"Grantee user not found.","messagePattern":"Grantee user not found\\.","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/db/models/emergency_access.rs","lineNumber":91,"sourceCode":"    pub async fn to_json_grantor_details(&self, conn: &DbConn) -> Value {\n        let grantor_user = User::find_by_uuid(&self.grantor_uuid, conn).await.expect(\"Grantor user not found.\");\n\n        json!({\n            \"id\": self.uuid,\n            \"status\": self.status,\n            \"type\": self.atype,\n            \"waitTimeDays\": self.wait_time_days,\n            \"grantorId\": grantor_user.uuid,\n            \"email\": grantor_user.email,\n            \"name\": grantor_user.name,\n            \"avatarColor\": grantor_user.avatar_color,\n            \"object\": \"emergencyAccessGrantorDetails\",\n        })\n    }\n\n    pub async fn to_json_grantee_details(&self, conn: &DbConn) -> Option<Value> {\n        let grantee_user = if let Some(grantee_uuid) = &self.grantee_uuid {\n            User::find_by_uuid(grantee_uuid, conn).await.expect(\"Grantee user not found.\")\n        } else {\n            let email = self.email.as_deref()?;\n            if let Some(user) = User::find_by_mail(email, conn).await {\n                user\n            } else {\n                // remove outstanding invitations which should not exist\n                Self::delete_all_by_grantee_email(email, conn).await.ok();\n                return None;\n            }\n        };\n\n        Some(json!({\n            \"id\": self.uuid,\n            \"status\": self.status,\n            \"type\": self.atype,\n            \"waitTimeDays\": self.wait_time_days,\n            \"granteeId\": grantee_user.uuid,\n            \"email\": grantee_user.email,","sourceCodeStart":73,"sourceCodeEnd":109,"githubUrl":"https://github.com/dani-garcia/vaultwarden/blob/cc67d644f62605cb46f4d16c4a2eed1a861cc8bb/src/db/models/emergency_access.rs#L73-L109","documentation":"Panic from `.expect()` in `EmergencyAccess::to_json_grantee_details` when the record has a grantee_uuid but `User::find_by_uuid` finds no such user. The code guarantees a grantee user for confirmed accesses; an orphaned grantee_uuid (deleted user, stale row) breaks that invariant and panics during JSON serialization.","triggerScenarios":"Serializing emergency access where the grantee account was deleted after being confirmed/invited; emergency_access rows with a grantee_uuid pointing to a purged user; partial DB restores leaving dangling references.","commonSituations":"User self-deletion or admin purge without cascade cleanup of emergency_access; invitation cleanup removed the user but not the link; manual DB manipulation.","solutions":["Find and remove orphaned rows: SELECT * FROM emergency_access WHERE grantee_uuid IS NOT NULL AND grantee_uuid NOT IN (SELECT uuid FROM users); delete them","Restore the missing grantee user from backup if it should exist","Check the code path that deletes users and ensure emergency_access rows are cleaned up (cascade delete)","Change the code to handle a missing grantee gracefully (skip the record or return an error) instead of expect()"],"exampleFix":"// before\nUser::find_by_uuid(grantee_uuid, conn).await.expect(\"Grantee user not found.\")\n// after\nUser::find_by_uuid(grantee_uuid, conn).await.ok_or_else(|| err!(\"Grantee user not found.\"))?","handlingStrategy":"validation","validationCode":"// before serializing, confirm the grantee exists when grantee_uuid is set\nSELECT 1 FROM users u JOIN emergency_access ea ON ea.grantee_uuid = u.uuid WHERE ea.uuid = ?;","typeGuard":null,"tryCatchPattern":"// Skip or error on orphaned grantee rows instead of panicking\nmatch catch_unwind(|| to_json_grantee_details(ea)) { Ok(v) => v, Err(_) => skip_record_with_warning() }","preventionTips":["Ensure user deletion code removes dependent emergency_access rows","Run orphaned-grantee consistency checks after partial restores","Validate invite/confirm flows leave grantee_uuid consistent with status","Keep DB backups to restore accidentally deleted grantees"],"tags":["database","referential-integrity","panic","emergency-access"],"backgroundTag":"record-not-found","analyzedSha":"cc67d644f62605cb46f4d16c4a2eed1a861cc8bb","analyzedAt":"2026-09-19T08:28:46.370Z","contentChangedAt":"2026-09-19T08:28:46.370Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}