use anyhow::{Context, Result};
use std::path::{Path, PathBuf};
pub(super) struct Home {
pub path: PathBuf,
}
impl Home {
pub fn create(real: &Path, aid_dir: &Path) -> Result<Self> {
std::fs::create_dir_all(aid_dir)?;
let real = real.canonicalize().context("Resolve real Codex home")?;
anyhow::ensure!(
real.join("sessions").is_dir() && real.join("state_5.sqlite").is_file(),
"Codex app-server requires existing sessions and state_5.sqlite in the real home"
);
let path = aid_dir.join("codex-home");
std::fs::create_dir_all(&path).context("Create stable Codex home")?;
anyhow::ensure!(
std::fs::symlink_metadata(&path)?.file_type().is_dir(),
"Codex mirror must be a directory, not a symlink"
);
anyhow::ensure!(
path.canonicalize()? != real,
"Codex mirror must differ from real home"
);
let home = Self { path };
home.refresh(&real)?;
for entry in std::fs::read_dir(real)? {
let entry = entry?;
let name = entry.file_name();
if name.to_string_lossy().starts_with("config.toml") {
continue;
}
#[cfg(unix)]
match std::os::unix::fs::symlink(entry.path(), home.path.join(&name)) {
Ok(()) => {}
Err(error) if error.kind() == std::io::ErrorKind::AlreadyExists => {
anyhow::ensure!(
std::fs::read_link(home.path.join(name))? == entry.path(),
"Codex mirror entry points at a different home"
);
}
Err(error) => return Err(error.into()),
}
#[cfg(not(unix))]
anyhow::bail!("Codex app-server home isolation requires Unix symlinks");
}
Ok(home)
}
fn refresh(&self, real: &Path) -> Result<()> {
for entry in std::fs::read_dir(&self.path)? {
let entry = entry?;
let name = entry.file_name();
let kind = entry.file_type()?;
let config = name.to_string_lossy().starts_with("config.toml");
if config && kind.is_file() {
match std::fs::remove_file(entry.path()) {
Ok(()) => {}
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
Err(error) => return Err(error.into()),
}
continue;
}
anyhow::ensure!(
!config,
"Codex mirror contains non-regular user configuration"
);
anyhow::ensure!(
kind.is_symlink(),
"Codex mirror contains local state invisible to the real home: {}",
name.to_string_lossy()
);
if !real.join(&name).try_exists()? {
match std::fs::remove_file(entry.path()) {
Ok(()) => {}
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
Err(error) => return Err(error.into()),
}
}
}
Ok(())
}
pub fn for_command(cmd: &std::process::Command) -> Result<Self> {
let real = cmd
.get_envs()
.find(|(key, _)| *key == "CODEX_HOME")
.and_then(|(_, value)| value)
.filter(|value| !value.is_empty())
.map(PathBuf::from)
.map(Ok)
.unwrap_or_else(crate::agent::codex::cli_config::codex_home)?;
Self::create(&real, &crate::support::paths::aid_dir())
}
}
#[cfg(test)]
#[path = "home_tests.rs"]
mod tests;
#[cfg(test)]
#[path = "home_dispatch_tests.rs"]
mod dispatch_tests;
#[cfg(test)]
#[path = "home_launch_tests.rs"]
mod launch_tests;