pub fn apply_global(profile: &str, dir: &str) -> std::io::Result<()> {
apply_global_impl(profile, dir)
}
#[cfg(target_os = "macos")]
fn apply_global_impl(profile: &str, dir: &str) -> std::io::Result<()> {
launchctl_setenv(profile, dir)
}
#[cfg(target_os = "macos")]
pub fn launchctl_setenv(_profile: &str, dir: &str) -> std::io::Result<()> {
use std::process::Command;
let status = Command::new("/bin/launchctl")
.args(["setenv", "CLAUDE_CONFIG_DIR", dir])
.status();
match status {
Ok(s) if s.success() => {}
Ok(s) => {
eprintln!(
"cas: launchctl setenv exited with {s} — GUI apps may not see the new profile until re-login"
);
}
Err(e) => {
eprintln!("cas: launchctl setenv failed: {e} — GUI apps may not see the new profile");
}
}
Ok(())
}
#[cfg(windows)]
fn apply_global_impl(profile: &str, dir: &str) -> std::io::Result<()> {
hkcu_setenv(profile, dir)
}
#[cfg(windows)]
pub fn hkcu_setenv(_profile: &str, dir: &str) -> std::io::Result<()> {
use windows_sys::Win32::{
Foundation::HWND,
System::Registry::{
RegCloseKey, RegOpenKeyExW, RegSetValueExW, HKEY, HKEY_CURRENT_USER, KEY_SET_VALUE,
REG_SZ,
},
UI::WindowsAndMessaging::{
SendMessageTimeoutW, HWND_BROADCAST, SMTO_ABORTIFHUNG, WM_SETTINGCHANGE,
},
};
let dir_wide: Vec<u16> = dir.encode_utf16().chain(std::iter::once(0)).collect();
let key_path: Vec<u16> = "Environment\0".encode_utf16().collect();
let value_name: Vec<u16> = "CLAUDE_CONFIG_DIR\0".encode_utf16().collect();
let env_str: Vec<u16> = "Environment\0".encode_utf16().collect();
let mut hkey: HKEY = 0;
let open_result = unsafe {
RegOpenKeyExW(
HKEY_CURRENT_USER,
key_path.as_ptr(),
0,
KEY_SET_VALUE,
&mut hkey,
)
};
if open_result != 0 {
eprintln!(
"cas: RegOpenKeyExW failed (0x{open_result:08X}) — HKCU\\Environment not updated"
);
return Ok(()); }
let set_result = unsafe {
RegSetValueExW(
hkey,
value_name.as_ptr(),
0,
REG_SZ,
dir_wide.as_ptr() as *const u8,
(dir_wide.len() * 2) as u32,
)
};
unsafe { RegCloseKey(hkey) };
if set_result != 0 {
eprintln!("cas: RegSetValueExW failed (0x{set_result:08X}) — HKCU\\Environment\\CLAUDE_CONFIG_DIR not updated");
return Ok(()); }
let mut result: usize = 0;
unsafe {
SendMessageTimeoutW(
HWND_BROADCAST as HWND,
WM_SETTINGCHANGE,
0,
env_str.as_ptr() as isize,
SMTO_ABORTIFHUNG,
5000,
&mut result,
);
}
Ok(())
}
#[cfg(all(unix, not(target_os = "macos")))]
fn apply_global_impl(_profile: &str, _dir: &str) -> std::io::Result<()> {
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn apply_global_does_not_panic() {
let result = apply_global("home", "/tmp/.claude.home");
assert!(
result.is_ok(),
"apply_global must not return hard error: {:?}",
result
);
}
#[cfg(target_os = "macos")]
#[test]
fn launchctl_setenv_does_not_panic_with_valid_args() {
let result = launchctl_setenv("home", "/tmp/.claude.home");
assert!(
result.is_ok(),
"launchctl_setenv should soft-fail, not hard-fail: {:?}",
result
);
}
#[cfg(all(unix, not(target_os = "macos")))]
#[test]
fn linux_apply_global_is_noop() {
let result = apply_global("home", "/tmp/.claude.home");
assert!(result.is_ok());
}
}