fix: macOS keychain infinite prompt loop (#6620)

Signed-off-by: sheikhlimon <sheikhlimon404@gmail.com>
This commit is contained in:
Sheikh Limon
2026-01-23 13:28:51 +06:00
committed by GitHub
parent 0602dc7ced
commit 698382c5ac
+50 -20
View File
@@ -12,7 +12,7 @@ use std::ffi::OsString;
use std::fs::OpenOptions; use std::fs::OpenOptions;
use std::io::Write; use std::io::Write;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::Mutex; use std::sync::{Arc, Mutex};
use thiserror::Error; use thiserror::Error;
const KEYRING_SERVICE: &str = "goose"; const KEYRING_SERVICE: &str = "goose";
@@ -105,6 +105,7 @@ pub struct Config {
config_path: PathBuf, config_path: PathBuf,
secrets: SecretStorage, secrets: SecretStorage,
guard: Mutex<()>, guard: Mutex<()>,
secrets_cache: Arc<Mutex<Option<HashMap<String, Value>>>>,
} }
enum SecretStorage { enum SecretStorage {
@@ -133,6 +134,7 @@ impl Default for Config {
config_path, config_path,
secrets, secrets,
guard: Mutex::new(()), guard: Mutex::new(()),
secrets_cache: Arc::new(Mutex::new(None)),
} }
} }
} }
@@ -236,6 +238,7 @@ impl Config {
service: service.to_string(), service: service.to_string(),
}, },
guard: Mutex::new(()), guard: Mutex::new(()),
secrets_cache: Arc::new(Mutex::new(None)),
}) })
} }
@@ -253,6 +256,7 @@ impl Config {
path: secrets_path.as_ref().to_path_buf(), path: secrets_path.as_ref().to_path_buf(),
}, },
guard: Mutex::new(()), guard: Mutex::new(()),
secrets_cache: Arc::new(Mutex::new(None)),
}) })
} }
@@ -540,28 +544,43 @@ impl Config {
} }
pub fn all_secrets(&self) -> Result<HashMap<String, Value>, ConfigError> { pub fn all_secrets(&self) -> Result<HashMap<String, Value>, ConfigError> {
match &self.secrets { let mut cache = self.secrets_cache.lock().unwrap();
SecretStorage::Keyring { service } => {
let result =
self.handle_keyring_operation(|entry| entry.get_password(), service, None);
match result { let values = if let Some(ref cached_secrets) = *cache {
Ok(content) => { cached_secrets.clone()
let values: HashMap<String, Value> = serde_json::from_str(&content)?; } else {
Ok(values) tracing::debug!("secrets cache miss, fetching from storage");
let loaded = match &self.secrets {
SecretStorage::Keyring { service } => {
let result =
self.handle_keyring_operation(|entry| entry.get_password(), service, None);
match result {
Ok(content) => {
let values: HashMap<String, Value> = serde_json::from_str(&content)?;
values
}
Err(ConfigError::FallbackToFileStorage) => {
self.fallback_to_file_storage()?
}
Err(ConfigError::KeyringError(msg))
if msg.contains("No entry found")
|| msg.contains("No matching entry found") =>
{
HashMap::new()
}
Err(e) => return Err(e),
} }
Err(ConfigError::FallbackToFileStorage) => self.fallback_to_file_storage(),
Err(ConfigError::KeyringError(msg))
if msg.contains("No entry found")
|| msg.contains("No matching entry found") =>
{
Ok(HashMap::new())
}
Err(e) => Err(e),
} }
} SecretStorage::File { path } => self.read_secrets_from_file(path)?,
SecretStorage::File { path } => self.read_secrets_from_file(path), };
}
*cache = Some(loaded.clone());
loaded
};
Ok(values)
} }
/// Parse an environment variable value into a JSON Value. /// Parse an environment variable value into a JSON Value.
@@ -786,6 +805,9 @@ impl Config {
std::fs::write(path, yaml_value)?; std::fs::write(path, yaml_value)?;
} }
}; };
self.invalidate_secrets_cache();
Ok(()) Ok(())
} }
@@ -820,6 +842,9 @@ impl Config {
std::fs::write(path, yaml_value)?; std::fs::write(path, yaml_value)?;
} }
}; };
self.invalidate_secrets_cache();
Ok(()) Ok(())
} }
@@ -858,6 +883,11 @@ impl Config {
Ok(()) Ok(())
} }
fn invalidate_secrets_cache(&self) {
let mut cache = self.secrets_cache.lock().unwrap();
*cache = None;
}
/// Check if an error string indicates a keyring availability issue that should trigger fallback /// Check if an error string indicates a keyring availability issue that should trigger fallback
fn is_keyring_availability_error(&self, error_str: &str) -> bool { fn is_keyring_availability_error(&self, error_str: &str) -> bool {
error_str.contains("keyring") error_str.contains("keyring")