{"record":{"id":"9a5e329a43bb3b39","repo":"Hmbown/CodeWhale","slug":"external-credential-owner-does-not-match-provider","errorCode":null,"errorMessage":"external credential owner does not match provider {}","messagePattern":"external credential owner does not match provider (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/config.rs","lineNumber":12849,"sourceCode":"    consent_provider: codewhale_config::ProviderKind,\n    source: codewhale_config::ExternalCredentialSource,\n    path: &Path,\n) -> Result<PathBuf> {\n    let expected = match provider {\n        ApiProvider::OpenaiCodex => (\n            codewhale_config::ProviderKind::OpenaiCodex,\n            codewhale_config::ExternalCredentialSource::CodexCli,\n        ),\n        ApiProvider::Xai => (\n            codewhale_config::ProviderKind::Xai,\n            codewhale_config::ExternalCredentialSource::GrokCli,\n        ),\n        _ => anyhow::bail!(\n            \"{} has no supported external credential owner\",\n            provider.as_str()\n        ),\n    };\n    anyhow::ensure!(\n        (consent_provider, source) == expected,\n        \"external credential owner does not match provider {}\",\n        provider.as_str()\n    );\n    let path = codewhale_config::resolve_external_credential_path(path)?;\n    let path_value = path.to_str().context(\n        \"external credential path cannot be persisted losslessly because it is not valid UTF-8\",\n    )?;\n    validate_external_credential_before_consent(consent_provider, source, &path).with_context(\n        || {\n            format!(\n                \"no usable {} credential was found, so read-only consent was not saved\",\n                source.owner_label()\n            )\n        },\n    )?;\n    let config_path = match config_path {\n        Some(path) => path.to_path_buf(),","sourceCodeStart":12831,"sourceCodeEnd":12867,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/config.rs#L12831-L12867","documentation":"When granting an external credential file access, the code computes an expected (consent_provider, source) pair for the provider being authorized and compares it with the pair attached to the consent record. This ensure! fires when the recorded consent's owner/origin does not match the provider it is being applied to, preventing a credential consent granted for one provider from being reused for another.","triggerScenarios":"Applying or persisting an external-credential consent whose (consent_provider, source) tuple differs from the expected pair for the given ApiProvider — e.g. replaying a stored consent under a different provider, or a config file whose consent fields were hand-edited.","commonSituations":"Migrating or copying consent entries between providers; hand-editing the config and moving a consent block under the wrong provider; a bug writing the consent record with the wrong provider field.","solutions":["Regenerate the consent for the correct provider instead of reusing the mismatched record","Remove the stale consent entry from the config and re-run the grant flow for this provider","Verify the provider argument matches the provider the credential file/consent was actually created for"],"exampleFix":"// before\napply_external_credential_consent(consent_for(ApiProvider::Xai), ApiProvider::OpenaiCodex)?;\n// after\nlet consent = consent_for(ApiProvider::OpenaiCodex); // consent owner must match target\napply_external_credential_consent(consent, ApiProvider::OpenaiCodex)?;","handlingStrategy":"try-catch","validationCode":"// verify consent owner matches target provider before applying\nanyhow::ensure!(consent.provider == target_provider, \"consent owner mismatch\");","typeGuard":null,"tryCatchPattern":"match apply_consent(consent, provider) {\n    Err(e) if e.to_string().starts_with(\"external credential owner does not match\") => {\n        regrant_consent_for(provider);\n    }\n    other => other?,\n}","preventionTips":["Never copy consent entries between providers in migrations","Re-run the grant flow per provider instead of reusing records","Validate consent.provider immediately after loading from disk"],"tags":["config","credentials","consent","mismatch"],"backgroundTag":"type-mismatch","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"}