dani-garcia/vaultwarden · error

Grantee user not found.

Error message

Grantee user not found.

What it means

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.

Solutions

  1. 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
  2. Restore the missing grantee user from backup if it should exist
  3. Check the code path that deletes users and ensure emergency_access rows are cleaned up (cascade delete)
  4. Change the code to handle a missing grantee gracefully (skip the record or return an error) instead of expect()

Example fix

// before
User::find_by_uuid(grantee_uuid, conn).await.expect("Grantee user not found.")
// after
User::find_by_uuid(grantee_uuid, conn).await.ok_or_else(|| err!("Grantee user not found."))?
Defensive patterns

Strategy: validation

Validate before calling

// before serializing, confirm the grantee exists when grantee_uuid is set
SELECT 1 FROM users u JOIN emergency_access ea ON ea.grantee_uuid = u.uuid WHERE ea.uuid = ?;

Try / catch

// Skip or error on orphaned grantee rows instead of panicking
match catch_unwind(|| to_json_grantee_details(ea)) { Ok(v) => v, Err(_) => skip_record_with_warning() }

Prevention

When it happens

Trigger: 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.

Common situations: User self-deletion or admin purge without cascade cleanup of emergency_access; invitation cleanup removed the user but not the link; manual DB manipulation.

Understand the failure class

Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.

Related errors


AI-assisted analysis of dani-garcia/vaultwarden@cc67d644f6 (2026-09-19). Data as JSON: /api/errors/49be704162f27882. Report an issue: GitHub.

Appendix: source

Thrown at src/db/models/emergency_access.rs:91

    pub async fn to_json_grantor_details(&self, conn: &DbConn) -> Value {
        let grantor_user = User::find_by_uuid(&self.grantor_uuid, conn).await.expect("Grantor user not found.");

        json!({
            "id": self.uuid,
            "status": self.status,
            "type": self.atype,
            "waitTimeDays": self.wait_time_days,
            "grantorId": grantor_user.uuid,
            "email": grantor_user.email,
            "name": grantor_user.name,
            "avatarColor": grantor_user.avatar_color,
            "object": "emergencyAccessGrantorDetails",
        })
    }

    pub async fn to_json_grantee_details(&self, conn: &DbConn) -> Option<Value> {
        let grantee_user = if let Some(grantee_uuid) = &self.grantee_uuid {
            User::find_by_uuid(grantee_uuid, conn).await.expect("Grantee user not found.")
        } else {
            let email = self.email.as_deref()?;
            if let Some(user) = User::find_by_mail(email, conn).await {
                user
            } else {
                // remove outstanding invitations which should not exist
                Self::delete_all_by_grantee_email(email, conn).await.ok();
                return None;
            }
        };

        Some(json!({
            "id": self.uuid,
            "status": self.status,
            "type": self.atype,
            "waitTimeDays": self.wait_time_days,
            "granteeId": grantee_user.uuid,
            "email": grantee_user.email,

View on GitHub (pinned to cc67d644f6)