zeroclaw-labs/zeroclaw · error · anyhow::Error

No credentials found for user '{user_id}'

Error message

No credentials found for user '{user_id}'

What it means

remove_credential loads the whole credential store and looks up user_id; when the user has no entry at all, it bails with 'No credentials found' (webauthn.rs:502-504). This is distinct from the sibling error 'Credential ... not found' (webauthn.rs:498-501), which fires when the user exists but that credential_id does not.

Source

Thrown at crates/zeroclaw-runtime/src/security/webauthn.rs:503

    }

    /// List all credentials for a user.
    pub fn list_credentials(&self, user_id: &str) -> Result<Vec<WebAuthnCredential>> {
        self.load_credentials_for_user(user_id)
    }

    /// Remove a credential by ID.
    pub fn remove_credential(&self, user_id: &str, credential_id: &str) -> Result<()> {
        let mut all = self.load_all_credentials()?;
        if let Some(user_creds) = all.get_mut(user_id) {
            let before = user_creds.len();
            user_creds.retain(|c| c.credential_id != credential_id);
            anyhow::ensure!(
                user_creds.len() < before,
                "Credential '{credential_id}' not found for user '{user_id}'"
            );
        } else {
            anyhow::bail!("No credentials found for user '{user_id}'");
        }
        self.save_all_credentials(&all)
    }

    // ── Private helpers ─────────────────────────────────────────

    fn generate_challenge(&self) -> Result<String> {
        let mut buf = [0u8; CHALLENGE_LEN];
        self.rng.fill(&mut buf).map_err(|_| {
            ::zeroclaw_log::record!(
                ERROR,
                ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Fail)
                    .with_outcome(::zeroclaw_log::EventOutcome::Failure),
                "webauthn challenge: RNG fill failed"
            );
            anyhow::Error::msg("Failed to generate random challenge")
        })?;
        Ok(URL_SAFE_NO_PAD.encode(buf))

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Verify the user_id matches exactly what enrollment used — list_credentials(user_id) shows what is stored
  2. If your delete flow should be idempotent, treat 'no credentials / not found' as success instead of an error
  3. Check that the credentials store path points to the same store used during registration

Example fix

// before
manager.remove_credential(&user_id, &credential_id)?;

// after: idempotent delete
let creds = manager.list_credentials(&user_id)?;
if creds.iter().any(|c| c.credential_id == credential_id) {
    manager.remove_credential(&user_id, &credential_id)?;
}
Ok(())
Defensive patterns

Strategy: validation

Validate before calling

let creds = manager.list_credentials(&user_id)?;
if creds.iter().any(|c| c.credential_id == credential_id) {
    manager.remove_credential(&user_id, &credential_id)?;
}
// else: nothing to delete — treat as success

Try / catch

If the pre-check is skipped, catch the error and treat both 'No credentials found' and 'not found for user' as idempotent success (or 404 in an admin API), logging the mismatched user_id for audit.

Prevention

When it happens

Trigger: remove_credential(user_id, credential_id) where the user never enrolled a WebAuthn credential, enrolled under a different user_id (case or prefix mismatch), or the credential store file was reset.

Common situations: Admin UI deletes by email while enrollment stored a UUID; the credentials JSON store was recreated after a wipe; double-submit after the first delete already removed the user's last credential.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/f2d25adfdbc06307. Report an issue: GitHub.