dani-garcia/vaultwarden · critical

Error loading ciphers

Error message

Error loading ciphers

What it means

find_by_user loads the user's visible ciphers via Diesel .load::<Self> and unwraps with .expect("Error loading ciphers"). Any database failure during this query panics with this message: connection loss, schema mismatch, or deserialization errors.

Solutions

  1. Run pending migrations (automatic on startup; check DB schema version table)
  2. Verify DB connectivity and that the ciphers table exists with expected columns
  3. Check server logs for the underlying Diesel error printed before the panic
  4. Restore schema consistency from backup or re-run migrations after schema drift
Defensive patterns

Strategy: try-catch

Validate before calling

// before calling find_by_user, confirm schema is current:
// run `vaultwarden` once and verify 'Migrations completed' in logs,
// or check the __diesel_schema_migrations table has the latest version.

Try / catch

match ciphers::find_by_user(&user_uuid, &conn).await {
  Ok(list) => list,
  Err(e) => { error!("Cipher load failed: {e}"); return api_err(500); }
}

Prevention

When it happens

Trigger: DB connection dropped mid-query, migrations not applied so the ciphers table/columns are missing, or a row whose column types/NULLs don't match the Cipher model fields.

Common situations: Upgrading Vaultwarden without running migrations (old schema); Postgres/MySQL restarted or pooled connections expired; manually edited schema diverging from diesel models.

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/dcb815b0b19e470a. Report an issue: GitHub.

Appendix: source

Thrown at src/db/models/cipher.rs:850

                    .filter(ciphers::user_uuid.eq(user_uuid)) // Cipher owner
                    .or_filter(users_organizations::access_all.eq(true)) // access_all in org
                    .or_filter(users_collections::user_uuid.eq(user_uuid)) // Access to collection
                    .or_filter(groups::access_all.eq(true)) // Access via groups
                    .or_filter(collections_groups::collections_uuid.is_not_null()) // Access via groups
                    .into_boxed();

                if !visible_only {
                    query = query.or_filter(
                        users_organizations::atype.le(MembershipType::Admin as i32), // Org admin/owner
                    );
                }

                // Only filter for one specific cipher
                if !cipher_uuids.is_empty() {
                    query = query.filter(ciphers::uuid.eq_any(cipher_uuids));
                }

                query.select(ciphers::all_columns).distinct().load::<Self>(conn).expect("Error loading ciphers")
            })
            .await
        } else {
            conn.run(move |conn| {
                let mut query = ciphers::table
                    .left_join(ciphers_collections::table.on(ciphers::uuid.eq(ciphers_collections::cipher_uuid)))
                    .left_join(
                        users_organizations::table.on(ciphers::organization_uuid
                            .eq(users_organizations::org_uuid.nullable())
                            .and(users_organizations::user_uuid.eq(user_uuid))
                            .and(users_organizations::status.eq(MembershipStatus::Confirmed as i32))),
                    )
                    .left_join(
                        users_collections::table.on(ciphers_collections::collection_uuid
                            .eq(users_collections::collection_uuid)
                            // Ensure that users_collections::user_uuid is NULL for unconfirmed users.
                            .and(users_organizations::user_uuid.eq(users_collections::user_uuid))),
                    )

View on GitHub (pinned to cc67d644f6)