dani-garcia/vaultwarden · error

Error loading devices

Error message

Error loading devices

What it means

Panic from an `.expect()` on a Diesel query in `Device::find_by_user`. The library considers any failure to load the `devices` rows for a user fatal and panics rather than returning an empty/failed result. It indicates a database-level failure (connectivity, schema mismatch), not simply that the user has no devices.

Solutions

  1. Verify DATABASE_URL connectivity and that the `devices` table exists with the expected columns
  2. Restart Vaultwarden to apply pending migrations and rebuild the connection pool
  3. Read the underlying Diesel error in the logs just above the panic and address it (connection, missing column, permissions)
  4. Check DB permissions for the configured user (SELECT on devices table)

Example fix

// before
.load::<Self>(conn).expect("Error loading devices")
// after
.load::<Self>(conn).map_err(|e| err!(DbError, "Error loading devices: {e}"))
Defensive patterns

Strategy: try-catch

Validate before calling

// check the devices table is reachable first
// SELECT 1 FROM devices LIMIT 1; — if this fails, skip device-dependent logic

Try / catch

// Catch or convert to a 503-style error instead of surfacing a raw panic
if let Err(e) = db_health_check() { return service_unavailable(e); }

Prevention

When it happens

Trigger: Calling `Device::find_by_user` while the database is down or the connection was dropped, after a failed/partial migration that removed or renamed the `devices` table/columns, or with a corrupted database file.

Common situations: Upgrade applied without migrations completing; DB credentials changed in config; SQLite data.db moved or corrupted; MySQL/Postgres restarted and pool holds stale connections.

Understand the failure class

Background: Database query failed: Internal Server Error 500s wrapping SQL, Prisma, and connection failures — what to check first — this error's family across 16 libraries.

Related errors


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

Appendix: source

Thrown at src/db/models/device.rs:206

                .first::<Self>(conn)
                .ok()
        })
        .await
    }

    pub async fn find_with_auth_request_by_user(user_uuid: &UserId, conn: &DbConn) -> Vec<DeviceWithAuthRequest> {
        let devices = Self::find_by_user(user_uuid, conn).await;
        let mut result = Vec::new();
        for device in devices {
            let auth_request = AuthRequest::find_by_user_and_requested_device(user_uuid, &device.uuid, conn).await;
            result.push(DeviceWithAuthRequest::from(device, auth_request));
        }
        result
    }

    pub async fn find_by_user(user_uuid: &UserId, conn: &DbConn) -> Vec<Self> {
        conn.run(move |conn| {
            devices::table.filter(devices::user_uuid.eq(user_uuid)).load::<Self>(conn).expect("Error loading devices")
        })
        .await
    }

    pub async fn find_by_uuid(uuid: &DeviceId, conn: &DbConn) -> Option<Self> {
        conn.run(move |conn| devices::table.filter(devices::uuid.eq(uuid)).first::<Self>(conn).ok()).await
    }

    pub async fn clear_push_token_by_uuid(uuid: &DeviceId, conn: &DbConn) -> EmptyResult {
        conn.run(move |conn| {
            diesel::update(devices::table)
                .filter(devices::uuid.eq(uuid))
                .set(devices::push_token.eq::<Option<String>>(None))
                .execute(conn)
                .map_res("Error removing push token")
        })
        .await
    }

View on GitHub (pinned to cc67d644f6)