{"record":{"id":"cef1ae14ff3cfd06","repo":"Hmbown/CodeWhale","slug":"external-credential-consent-source-mismatch-for","errorCode":null,"errorMessage":"external credential consent source mismatch for {} (expected {})","messagePattern":"external credential consent source mismatch for (.+?) \\(expected (.+?)\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/config/src/external_credentials.rs","lineNumber":389,"sourceCode":"                provider.as_str()\n            );\n        }\n        if self.consent_version != EXTERNAL_CREDENTIAL_CONSENT_VERSION {\n            bail!(\n                \"external credential consent for {} uses unsupported version {}; revoke and consent again\",\n                provider.as_str(),\n                self.consent_version\n            );\n        }\n        if self.provider != provider.as_str() {\n            bail!(\n                \"external credential consent is scoped to provider {:?}, not {}\",\n                self.provider,\n                provider.as_str()\n            );\n        }\n        if self.source != source {\n            bail!(\n                \"external credential consent source mismatch for {} (expected {})\",\n                provider.as_str(),\n                source.as_str()\n            );\n        }\n        if !self.path.is_absolute() {\n            bail!(\n                \"external credential consent path for {} must be absolute\",\n                provider.as_str()\n            );\n        }\n        let normalized = resolve_external_credential_path(&self.path)?;\n        if normalized != self.path {\n            bail!(\n                \"external credential consent path for {} must be lexically normalized: {}\",\n                provider.as_str(),\n                quote_os_path(&self.path)\n            );","sourceCodeStart":371,"sourceCodeEnd":407,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/config/src/external_credentials.rs#L371-L407","documentation":"Thrown by `ExternalCredentialConsentToml::validate_read_scope` when the consent record's `source` field does not equal the source argument of the read. A consent grant is bound to both a provider and a source (where the credential came from), so a grant issued for one source cannot authorize a read attributed to another.","triggerScenarios":"Calling `read_grant` with a `source` argument whose `as_str()` differs from `self.source` on the stored consent record — e.g. credential obtained from OAuth vs an API key import but consent recorded for the other source.","commonSituations":"The credential's acquisition path changed between consent and use (user re-authenticated via a different source); a consent record was hand-edited; a new source variant was introduced and old consents were recorded under a different source string.","solutions":["Revoke the existing consent and run the consent flow again so the record's source matches the current read.","Check which source produced the current credential and read via that same source.","Inspect the consent TOML's `source` field and correct the caller's source argument if it is wrong."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"fn consent_source_matches(consent: &ExternalCredentialConsentToml, source: &CredentialSource) -> bool {\n    consent.source == *source\n}","typeGuard":null,"tryCatchPattern":"match store.read_grant(provider, source, &path) {\n    Err(e) if e.to_string().contains(\"source mismatch\") => {\n        // re-consent so the recorded source matches the credential's actual source\n        store.revoke(provider)?;\n        run_consent_flow(provider)?;\n    }\n    other => other,\n}","preventionTips":["Read credentials through the same source that produced them at consent time.","Re-consent whenever the credential acquisition method changes.","Do not hand-edit the `source` field of consent records."],"tags":["config","credentials","consent","validation"],"backgroundTag":"invalid-config-value","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}