dani-garcia/vaultwarden · error

Error getting grantee

Error message

Error getting grantee

What it means

Panic from `.expect()` in `EmergencyAccess::update_access_status_and_save` when `self.grantee_uuid` is None. Only confirmed emergency accesses have a grantee; calling this status-update method on an invited/pending record (grantee_uuid is NULL) violates the invariant and panics before touching the database.

Solutions

  1. Ensure the record is in Confirmed state (grantee_uuid set) before calling update_access_status_and_save; check status first
  2. Clean up emergency_access rows with NULL grantee_uuid that should not exist
  3. Guard the method: return an error instead of expect when grantee_uuid is None
  4. Check for concurrent deletions — re-fetch the record and verify grantee before updating

Example fix

// before
User::update_uuid_revision(self.grantee_uuid.as_ref().expect("Error getting grantee"), conn).await;
// after
let grantee = self.grantee_uuid.as_ref().ok_or_else(|| err!("Error getting grantee"))?;
User::update_uuid_revision(grantee, conn).await;
Defensive patterns

Strategy: validation

Validate before calling

// before updating, require a confirmed record with a grantee
// if ea.grantee_uuid is None or ea.status != Confirmed -> do not call update_access_status_and_save

Try / catch

// Guard the call site
if record.grantee_uuid.is_some() { record.update_access_status_and_save(...).await?; } else { return Err(invalid_state); }

Prevention

When it happens

Trigger: Calling `update_access_status_and_save` on an EmergencyAccess whose grantee_uuid is None — e.g. accepting/confirming a recovery on an invitation row that was never linked to a grantee user, or a race where the grantee was removed first.

Common situations: Client/API calling the emergency-access accept or status endpoint twice or on an already-removed grantee; concurrent grantee deletion during status update; data imported from backup with NULL grantee_uuid on records assumed confirmed.

Related errors


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

Appendix: source

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

                diesel::insert_into(emergency_access::table)
                    .values(&*self)
                    .on_conflict(emergency_access::uuid)
                    .do_update()
                    .set(&*self)
                    .execute(conn)
                    .map_res("Error saving emergency access")
            }
        }
    }

    pub async fn update_access_status_and_save(
        &mut self,
        status: i32,
        date: &NaiveDateTime,
        conn: &DbConn,
    ) -> EmptyResult {
        // Update the grantee so that it will refresh it's status.
        User::update_uuid_revision(self.grantee_uuid.as_ref().expect("Error getting grantee"), conn).await;
        self.status = status;
        date.clone_into(&mut self.updated_at);

        conn.run(move |conn| {
            crate::util::retry(
                || {
                    diesel::update(emergency_access::table.filter(emergency_access::uuid.eq(&self.uuid)))
                        .set((emergency_access::status.eq(status), emergency_access::updated_at.eq(date)))
                        .execute(conn)
                },
                10,
            )
            .map_res("Error updating emergency access status")
        })
        .await
    }

    pub async fn update_last_notification_date_and_save(&mut self, date: &NaiveDateTime, conn: &DbConn) -> EmptyResult {

View on GitHub (pinned to cc67d644f6)