Hmbown/CodeWhale · error · anyhow::Error
external credential consent source mismatch for {} (expected
Error message
external credential consent source mismatch for {} (expected {}) What it means
Part of the exact-tuple validation in validate_read_scope(): the consent's stored ExternalCredentialSource must equal the source the caller is requesting (e.g. a path-based file source vs another backend). A mismatch means the grant was minted for a different acquisition mechanism and is refused before any capability is issued.
Source
Thrown at crates/config/src/external_credentials.rs:428
provider.as_str()
);
}
if self.consent_version != EXTERNAL_CREDENTIAL_CONSENT_VERSION {
bail!(
"external credential consent for {} uses unsupported version {}; revoke and consent again",
provider.as_str(),
self.consent_version
);
}
if self.provider != provider.as_str() {
bail!(
"external credential consent is scoped to provider {:?}, not {}",
self.provider,
provider.as_str()
);
}
if self.source != source {
bail!(
"external credential consent source mismatch for {} (expected {})",
provider.as_str(),
source.as_str()
);
}
if !self.path.is_absolute() {
bail!(
"external credential consent path for {} must be absolute",
provider.as_str()
);
}
let normalized = resolve_external_credential_path(&self.path)?;
if normalized != self.path {
bail!(
"external credential consent path for {} must be lexically normalized: {}",
provider.as_str(),
quote_os_path(&self.path)
);View on GitHub (pinned to 0c42157ee5)
Solutions
- Re-create the consent with the source you now actually use
- Align the code path and the consent: validate with the same source value the consent was created under
Example fix
# before [[external_credentials]] provider = "acme" source = "keyring" # but the caller requests the path source # after [[external_credentials]] provider = "acme" source = "path" path = "/keys/acme.env"
Defensive patterns
Strategy: validation
Validate before calling
if consent.source != requested_source {
// grant minted for a different acquisition mechanism; re-consent
return Err(anyhow!("consent source mismatch: recreate for {requested_source:?}"));
} Type guard
fn matches_source(consent: &ExternalCredentialConsent, s: ExternalCredentialSource) -> bool {
consent.source == s
} Try / catch
match consent.validate_read_scope(provider, source, &path) {
Ok(()) => read_external_credential(&path),
Err(e) if e.to_string().contains("source mismatch") => {
reconsent_with_source(provider, source, &path).await
}
Err(e) => Err(e),
} Prevention
- When switching a provider's credential mechanism, re-consent under the new source
- Do not mix keyring and path consents for the same provider entry
- Validate the full (provider, source, path) tuple in tooling before reads
When it happens
Trigger: A consent record created for source = "path" is validated with source = another ExternalCredentialSource variant (or vice versa) — commonly after config copying or after a code path switched how it resolves the same provider's credential.
Common situations: Reusing a consent block when switching a provider from a file-based credential to a keyring/other source (or the reverse); edits to the source field; version changes renaming source variants so deserialized values no longer match.
Related errors
- external credential path escapes its absolute root: {}
- external credential access is disabled for {}
- managed external credential access is unsupported for {}; no
- external credential consent for {} uses unsupported version
- external credential consent is scoped to provider {:?}, not
AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20).
Data as JSON: /api/errors/cef1ae14ff3cfd06.
Report an issue: GitHub.