1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
use std::path::PathBuf;
/// Resolve the user's home directory in a way that is:
/// - Override-friendly for CI/tests (HOME/USERPROFILE)
/// - Still correct in normal interactive installs (fallback to `dirs::home_dir()`)
pub fn resolve_home_dir() -> Option<PathBuf> {
if let Ok(home) = std::env::var("HOME") {
let trimmed = home.trim();
if !trimmed.is_empty() {
return Some(PathBuf::from(trimmed));
}
}
#[cfg(windows)]
{
if let Ok(profile) = std::env::var("USERPROFILE") {
let trimmed = profile.trim();
if !trimmed.is_empty() {
return Some(PathBuf::from(trimmed));
}
}
if let (Ok(drive), Ok(path)) = (std::env::var("HOMEDRIVE"), std::env::var("HOMEPATH")) {
if !drive.trim().is_empty() && !path.trim().is_empty() {
return Some(PathBuf::from(format!("{}{}", drive.trim(), path.trim())));
}
}
}
dirs::home_dir()
}
/// Resolve the Codex config directory.
/// Respects `CODEX_HOME` env var (official Codex CLI feature).
/// Falls back to `~/.codex` when unset or empty.
pub fn resolve_codex_dir() -> Option<PathBuf> {
if let Ok(val) = std::env::var("CODEX_HOME") {
let trimmed = val.trim();
if !trimmed.is_empty() {
return Some(PathBuf::from(trimmed));
}
}
resolve_home_dir().map(|h| h.join(".codex"))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn resolve_codex_dir_respects_env_var() {
let _guard = env_lock();
std::env::set_var("CODEX_HOME", "/tmp/custom-codex");
let result = resolve_codex_dir();
assert_eq!(result, Some(PathBuf::from("/tmp/custom-codex")));
std::env::remove_var("CODEX_HOME");
}
#[test]
fn resolve_codex_dir_ignores_empty_env() {
let _guard = env_lock();
std::env::set_var("CODEX_HOME", " ");
let result = resolve_codex_dir();
assert!(result.is_some());
assert!(result.unwrap().ends_with(".codex"));
std::env::remove_var("CODEX_HOME");
}
#[test]
fn resolve_codex_dir_falls_back_to_home() {
let _guard = env_lock();
std::env::remove_var("CODEX_HOME");
let result = resolve_codex_dir();
assert!(result.is_some());
assert!(result.unwrap().ends_with(".codex"));
}
fn env_lock() -> std::sync::MutexGuard<'static, ()> {
static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
LOCK.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
}