fix: batch of quick fixes (#417, #338, #330, #358, #419, #344) (#428)

- #417: Add Docker auto-start login item hint for macOS in setup wizard
- #338: Add clippy.toml with complexity thresholds for AI-assisted dev
- #330: Add structured FallbackFailed error variant to ExtensionError
- #358: Revoke credential mappings on extension removal (SharedCredentialRegistry)
- #419: Detect conflicting cloudflared services during tunnel setup
- #344: Improve embedding auth failure warning with configuration hint

Co-authored-by: Claude Opus 4.6 <[email protected]>
This commit is contained in:
Zaki Manian
2026-03-01 09:01:07 +00:00
committed by GitHub
co-authored by Claude Opus 4.6
parent fa52df593d
commit 7481aea083
8 changed files with 255 additions and 21 deletions
+48 -1
View File
@@ -77,7 +77,7 @@ impl SharedCredentialRegistry {
}
}
/// Add credential mappings (called when WASM tools register).
/// Add credential mappings tagged with an extension name (called when WASM tools register).
pub fn add_mappings(&self, mappings: impl IntoIterator<Item = CredentialMapping>) {
match self.mappings.write() {
Ok(mut guard) => {
@@ -93,6 +93,23 @@ impl SharedCredentialRegistry {
}
}
/// Remove all credential mappings whose `secret_name` matches any of the given names.
///
/// Called when an extension is unregistered/deactivated so its credential
/// injection authority does not outlive the extension.
pub fn remove_mappings_for_secrets(&self, secret_names: &[String]) {
let mut guard = match self.mappings.write() {
Ok(guard) => guard,
Err(poisoned) => {
tracing::warn!(
"SharedCredentialRegistry RwLock poisoned during remove_mappings_for_secrets; recovering"
);
poisoned.into_inner()
}
};
guard.retain(|m| !secret_names.contains(&m.secret_name));
}
/// Check if any credential mapping matches this host (sync, for requires_approval).
pub fn has_credentials_for_host(&self, host: &str) -> bool {
let guard = match self.mappings.read() {
@@ -564,6 +581,36 @@ mod tests {
assert_eq!(found.len(), 2);
}
#[test]
fn test_shared_registry_remove_mappings_for_secrets() {
let registry = SharedCredentialRegistry::new();
registry.add_mappings(vec![
CredentialMapping::bearer("openai_key", "api.openai.com"),
CredentialMapping::bearer("gh_token", "*.github.com"),
CredentialMapping::header("openai_org", "OpenAI-Organization", "api.openai.com"),
]);
assert_eq!(registry.find_for_host("api.openai.com").len(), 2);
assert!(registry.has_credentials_for_host("api.github.com"));
// Remove only mappings for openai secrets
registry.remove_mappings_for_secrets(&["openai_key".to_string(), "openai_org".to_string()]);
// OpenAI mappings should be gone
assert!(registry.find_for_host("api.openai.com").is_empty());
// GitHub mapping should remain
assert!(registry.has_credentials_for_host("api.github.com"));
}
#[test]
fn test_shared_registry_remove_nonexistent_is_noop() {
let registry = SharedCredentialRegistry::new();
registry.add_mappings(vec![CredentialMapping::bearer("key1", "api.example.com")]);
registry.remove_mappings_for_secrets(&["nonexistent".to_string()]);
assert_eq!(registry.find_for_host("api.example.com").len(), 1);
}
#[test]
fn test_shared_registry_thread_safety() {
use std::sync::Arc;