Hmbown/CodeWhale · error

external credential consent is scoped to provider

Error message

external credential consent is scoped to provider {:?}, not {}

What it means

Thrown by `ExternalCredentialConsentToml::validate_read_scope` when a stored consent record names a different provider than the one whose credentials are being read. Consent grants are provider-scoped; a record created for one provider can never authorize a read for another. This is a safety check preventing a consent record from being replayed across providers.

Solutions

  1. Re-run the consent flow for the provider you are actually reading so a correctly-scoped consent record is created.
  2. Revoke the stale consent (delete the consent record) and consent again with the matching provider.
  3. Verify the provider argument passed to read_grant/consent-status matches the provider the user actually consented to.

Example fix

// before: reading google creds with an antigravity-scoped consent
let grant = store.read_grant(ProviderKind::Google, source, &path)?;
// after: revoke the stale record and re-consent for google, or pass
// the provider the consent was actually created for
let grant = store.read_grant(ProviderKind::Antigravity, source, &path)?;
Defensive patterns

Strategy: validation

Validate before calling

fn consent_matches(consent: &ExternalCredentialConsentToml, provider: ProviderKind) -> bool {
    consent.provider == provider.as_str()
}

Try / catch

match store.read_grant(provider, source, &path) {
    Ok(grant) => use_grant(grant),
    Err(e) if e.to_string().contains("scoped to provider") => {
        // stale consent for another provider: revoke and re-run consent flow
        store.revoke(provider)?;
        run_consent_flow(provider)?;
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling `read_grant` (directly or via `external_credential_consent_status`) with a `provider` argument whose `as_str()` differs from the `provider` field persisted in the consent record.

Common situations: A consent file created for one provider (e.g. "google") is being consulted for another (e.g. "openrouter") after a config rename or copy; a user manually edited or moved a consent TOML between provider sections; provider identifier changed (e.g. legacy "antigravity" vs "google") but the old consent record remains.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/621c0891340a01d2. Report an issue: GitHub.

Appendix: source

Thrown at crates/config/src/external_credentials.rs:382

                "external credential access is disabled for {}",
                provider.as_str()
            );
        }
        if self.access == ExternalCredentialAccess::Managed {
            bail!(
                "managed external credential access is unsupported for {}; no schema-safe preservation adapter is available",
                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()
            );
        }

View on GitHub (pinned to 73e0f67d83)