Skip to main content

codex_utils_home_dir/
lib.rs

1use codex_utils_absolute_path::AbsolutePathBuf;
2use dirs::home_dir;
3use std::path::PathBuf;
4
5/// Returns the path to the Codex configuration directory, which can be
6/// specified by the `CODEX_HOME` environment variable. If not set, defaults to
7/// `~/.codex`.
8///
9/// - If `CODEX_HOME` is set, the value must exist and be a directory. The
10///   value will be canonicalized and this function will Err otherwise.
11/// - If `CODEX_HOME` is not set, this function does not verify that the
12///   directory exists.
13pub fn find_codex_home() -> std::io::Result<AbsolutePathBuf> {
14    let codex_home_env = std::env::var("CODEX_HOME")
15        .ok()
16        .filter(|val| !val.is_empty());
17    find_codex_home_from_env(codex_home_env.as_deref())
18}
19
20fn find_codex_home_from_env(codex_home_env: Option<&str>) -> std::io::Result<AbsolutePathBuf> {
21    // Honor the `CODEX_HOME` environment variable when it is set to allow users
22    // (and tests) to override the default location.
23    match codex_home_env {
24        Some(val) => {
25            let path = PathBuf::from(val);
26            let metadata = std::fs::metadata(&path).map_err(|err| match err.kind() {
27                std::io::ErrorKind::NotFound => std::io::Error::new(
28                    std::io::ErrorKind::NotFound,
29                    format!("CODEX_HOME points to {val:?}, but that path does not exist"),
30                ),
31                _ => std::io::Error::new(
32                    err.kind(),
33                    format!("failed to read CODEX_HOME {val:?}: {err}"),
34                ),
35            })?;
36
37            if !metadata.is_dir() {
38                Err(std::io::Error::new(
39                    std::io::ErrorKind::InvalidInput,
40                    format!("CODEX_HOME points to {val:?}, but that path is not a directory"),
41                ))
42            } else {
43                let canonical = path.canonicalize().map_err(|err| {
44                    std::io::Error::new(
45                        err.kind(),
46                        format!("failed to canonicalize CODEX_HOME {val:?}: {err}"),
47                    )
48                })?;
49                AbsolutePathBuf::from_absolute_path(canonical)
50            }
51        }
52        None => {
53            let mut p = home_dir().ok_or_else(|| {
54                std::io::Error::new(
55                    std::io::ErrorKind::NotFound,
56                    "Could not find home directory",
57                )
58            })?;
59            p.push(".codex");
60            AbsolutePathBuf::from_absolute_path(p)
61        }
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use super::find_codex_home_from_env;
68    use codex_utils_absolute_path::AbsolutePathBuf;
69    use dirs::home_dir;
70    use pretty_assertions::assert_eq;
71    use std::fs;
72    use std::io::ErrorKind;
73    use tempfile::TempDir;
74
75    #[test]
76    fn find_codex_home_env_missing_path_is_fatal() {
77        let temp_home = TempDir::new().expect("temp home");
78        let missing = temp_home.path().join("missing-codex-home");
79        let missing_str = missing
80            .to_str()
81            .expect("missing codex home path should be valid utf-8");
82
83        let err = find_codex_home_from_env(Some(missing_str)).expect_err("missing CODEX_HOME");
84        assert_eq!(err.kind(), ErrorKind::NotFound);
85        assert!(
86            err.to_string().contains("CODEX_HOME"),
87            "unexpected error: {err}"
88        );
89    }
90
91    #[test]
92    fn find_codex_home_env_file_path_is_fatal() {
93        let temp_home = TempDir::new().expect("temp home");
94        let file_path = temp_home.path().join("codex-home.txt");
95        fs::write(&file_path, "not a directory").expect("write temp file");
96        let file_str = file_path
97            .to_str()
98            .expect("file codex home path should be valid utf-8");
99
100        let err = find_codex_home_from_env(Some(file_str)).expect_err("file CODEX_HOME");
101        assert_eq!(err.kind(), ErrorKind::InvalidInput);
102        assert!(
103            err.to_string().contains("not a directory"),
104            "unexpected error: {err}"
105        );
106    }
107
108    #[test]
109    fn find_codex_home_env_valid_directory_canonicalizes() {
110        let temp_home = TempDir::new().expect("temp home");
111        let temp_str = temp_home
112            .path()
113            .to_str()
114            .expect("temp codex home path should be valid utf-8");
115
116        let resolved = find_codex_home_from_env(Some(temp_str)).expect("valid CODEX_HOME");
117        let expected = temp_home
118            .path()
119            .canonicalize()
120            .expect("canonicalize temp home");
121        let expected = AbsolutePathBuf::from_absolute_path(expected).expect("absolute home");
122        assert_eq!(resolved, expected);
123    }
124
125    #[test]
126    fn find_codex_home_without_env_uses_default_home_dir() {
127        let resolved =
128            find_codex_home_from_env(/*codex_home_env*/ None).expect("default CODEX_HOME");
129        let mut expected = home_dir().expect("home dir");
130        expected.push(".codex");
131        let expected = AbsolutePathBuf::from_absolute_path(expected).expect("absolute home");
132        assert_eq!(resolved, expected);
133    }
134}