{"record":{"id":"3755dd6fa4d70179","repo":"Hmbown/CodeWhale","slug":"external-credential-access-for-is-dormant-until","errorCode":null,"errorMessage":"external credential access for {} is dormant until that provider is explicitly selected","messagePattern":"external credential access for (.+?) is dormant until that provider is explicitly selected","errorType":"console","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/config.rs","lineNumber":5966,"sourceCode":"            })\n    }\n\n    /// Mint a read capability for the exact external credential path selected\n    /// when consent was granted.\n    ///\n    /// Path resolution itself is side-effect free. The returned capability is\n    /// required by every external credential adapter before it may stat or\n    /// read the selected file. `suggested_path` is used only in disabled-mode\n    /// guidance; an existing grant remains pinned to its persisted path even\n    /// if ambient CLI-home environment variables change later.\n    pub(crate) fn external_credential_read_grant(\n        &self,\n        provider: ApiProvider,\n        source: codewhale_config::ExternalCredentialSource,\n        suggested_path: &Path,\n    ) -> Result<codewhale_config::ExternalCredentialReadGrant> {\n        if provider != self.api_provider() {\n            anyhow::bail!(\n                \"external credential access for {} is dormant until that provider is explicitly selected\",\n                provider.display_name()\n            );\n        }\n        let kind = provider\n            .metadata()\n            .map(codewhale_config::provider::Provider::kind)\n            .context(\"external credentials are unsupported for this provider\")?;\n        let consent = self\n            .provider_config_for(provider)\n            .and_then(|entry| entry.external_credentials.as_ref())\n            .with_context(|| {\n                format!(\n                    \"External credentials owned by {} are disabled for {}. To allow read-only access to this exact file, run:\\n  codewhale auth external-consent --provider {} --mode read-only --path {}\",\n                    source.as_str(),\n                    provider.display_name(),\n                    kind.as_str(),\n                    codewhale_config::quote_os_path(suggested_path)","sourceCodeStart":5948,"sourceCodeEnd":5984,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/8880682c63083a91624de936797efa3ce9e498fd/crates/tui/src/config.rs#L5948-L5984","documentation":"Config::external_credential_read_grant() mints the capability required before any external CLI credential file may be stat/read. It refuses when the requested provider differs from the active api_provider(): access to another CLI's credentials stays dormant until that provider is explicitly selected. This is a deliberate least-privilege gate, not a bug.","triggerScenarios":"Calling external_credential_read_grant(ApiProvider::Xai, ...) while the active provider is anthropic; provider-picker or diagnostic code probing credentials for every known provider regardless of selection.","commonSituations":"Bulk credential scans/status pages iterating all providers; code reused from a context where the provider was selected; tests that forget to set the provider before requesting a grant.","solutions":["Select the target provider first (provider = in config, or the --provider CLI flag) before requesting its external credentials.","In code, skip the grant when provider != config.api_provider() instead of treating it as an error.","If you only need to know whether consent exists, use external_credential_read_consent_configured(), which never mints a grant and never stats the file."],"exampleFix":"// before\nlet grant = config.external_credential_read_grant(ApiProvider::Xai, source, &path)?;\n\n// after\nif config.api_provider() != ApiProvider::Xai {\n    return Ok(None); // external credentials dormant unless provider is selected\n}\nlet grant = config.external_credential_read_grant(ApiProvider::Xai, source, &path)?;","handlingStrategy":"validation","validationCode":"// before touching external credentials, gate on the active provider\nfn grant_if_active(\n    config: &Config,\n    provider: ApiProvider,\n    source: ExternalCredentialSource,\n    path: &Path,\n) -> Result<Option<ExternalCredentialReadGrant>> {\n    if provider != config.api_provider() {\n        return Ok(None); // dormant by design — not an error\n    }\n    Ok(Some(config.external_credential_read_grant(provider, source, path)?))\n}","typeGuard":"fn provider_credentials_are_eligible(config: &Config, provider: ApiProvider) -> bool {\n    config.api_provider() == provider\n}","tryCatchPattern":"match config.external_credential_read_grant(provider, source, &path) {\n    Ok(grant) => Some(grant),\n    Err(e) if e.to_string().contains(\"dormant until that provider is explicitly selected\") => {\n        None // expected for non-selected providers; do not surface as failure\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Never iterate all providers minting read grants; only the selected provider is eligible.","Use external_credential_read_consent_configured() for passive status checks — it never stats files or mints capabilities.","In tests, set the provider before requesting grants."],"tags":["rust","security","credentials","provider","capability","external-consent"],"backgroundTag":null,"analyzedSha":"8880682c63083a91624de936797efa3ce9e498fd","analyzedAt":"2026-08-16T11:31:27.956Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}