Hmbown/CodeWhale · error

external credential path changed for

Error message

external credential path changed for {}; consent covers {}, current path is {}

What it means

Thrown by `ExternalCredentialConsentToml::validate_read_scope` when the consent record's path no longer equals the currently resolved credential path — the file the consent covers has moved, been replaced by a symlink, or been renamed since consent was given. The grant is invalidated rather than silently following the new location.

Solutions

  1. Revoke the old consent and re-consent against the credential's current location.
  2. Restore the credential file to the path covered by the existing consent (or fix the symlink).
  3. If a migration moved credential paths intentionally, re-run the consent flow as part of the migration.

Example fix

// before: consent covers old path after file moved
mv ~/.codewhale/creds/google.json ~/secrets/google.json
// after: point the credential back or re-consent
ln -s ~/secrets/google.json ~/.codewhale/creds/google.json  # or revoke + re-consent
Defensive patterns

Strategy: validation

Validate before calling

fn consent_covers_current_location(consent: &ExternalCredentialConsentToml) -> anyhow::Result<bool> {
    Ok(resolve_external_credential_path(&consent.path)? == consent.path)
}

Try / catch

match store.read_grant(provider, source, &path) {
    Err(e) if e.to_string().contains("path changed") => {
        // credential moved since consent: revoke and re-consent at the new location
        store.revoke(provider)?;
        run_consent_flow(provider)?;
    }
    other => other,
}

Prevention

When it happens

Trigger: Calling `read_grant` where `resolved_path` (freshly resolved at read time) differs from `self.path` recorded at consent time — e.g. the credential file was moved, renamed, or is now reached via a different symlink.

Common situations: User moved/rotated their credential file after consenting; the credential store migrated from one location to another in an upgrade; a symlink target changed.

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

Appendix: source

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

                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)
            );
        }
        if self.path != resolved_path {
            bail!(
                "external credential path changed for {}; consent covers {}, current path is {}",
                provider.as_str(),
                quote_os_path(&self.path),
                quote_os_path(resolved_path)
            );
        }
        Ok(())
    }

    /// Validate and mint the read capability consumed by credential adapters.
    /// No filesystem operation occurs while validating the policy.
    pub fn read_grant(
        &self,
        provider: ProviderKind,
        source: ExternalCredentialSource,
        resolved_path: &Path,
    ) -> Result<ExternalCredentialReadGrant> {
        self.validate_read_scope(provider, source, resolved_path)?;

View on GitHub (pinned to 73e0f67d83)