BigPizzaV3/CodexPlusPlus · critical · anyhow::Error
拒绝删除文件系统根目录:
Error message
拒绝删除文件系统根目录:{} What it means
Thrown by `ensure_safe_recursive_removal` when the requested deletion target is empty or normalizes to a filesystem root (`/` on Unix; `C:\`, `\\?\C:\`, or UNC `\\server\share\` variants on Windows). This guard prevents a recursive delete from wiping an entire drive or share, including Windows cases where a plain `/` normalizes to the current drive root.
Solutions
- Fix the caller so it never passes an empty or root path — ensure a real subdirectory is computed before deletion
- Validate the target path before calling (non-empty, has a parent, not `has_root()` alone)
- Pass an explicit CODEX_HOME so normalization has a correct baseline and the other guards work
- Log/inspect `target.display()` (included in the error) to find where the root path originated
Example fix
// before
let target = std::env::var("CODEX_TARGET_DIR").unwrap_or_default(); // empty
ensure_safe_recursive_removal(Path::new(&target), &codex_home)?;
// after
let target = std::env::var("CODEX_TARGET_DIR").context("CODEX_TARGET_DIR must be set")?;
let path = Path::new(&target);
ensure!(path.is_absolute() && path.parent().is_some(), "refusing non-subdirectory target");
ensure_safe_recursive_removal(path, &codex_home)?; Defensive patterns
Strategy: validation
Validate before calling
fn safe_removal_target(p: &Path) -> Result<(), String> {
if p.as_os_str().is_empty() { return Err("empty path".into()); }
if p.has_root() && p.parent().is_none() { return Err("filesystem root".into()); }
Ok(())
} Prevention
- Never pass user-supplied paths straight into recursive removal
- Require absolute, non-root subdirectory targets
- Be extra careful with Windows verbatim (\\?\) and UNC paths
- Keep the empty-path check — an empty Path is normalized to CWD, not caught by root checks
When it happens
Trigger: Calling the removal API with an empty path, `Path::new("/")`, a drive root like `C:\` or `C:`, a `\\?\C:\` verbatim path, or a UNC share root `\\server\share\`.
Common situations: Uninitialized/default empty path variables reaching the delete call, path-joining bugs that collapse to `/`, or user input containing a drive root on Windows.
Understand the failure class
Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.
Related errors
- error.to_string()
- 拒绝递归删除 CODEX_HOME 本身(
- 拒绝删除 CODEX_HOME 的祖先目录
- File is a reparse point
- Parent directory is a reparse point
AI-assisted analysis of BigPizzaV3/CodexPlusPlus@b1ed92e5e4 (2026-09-19).
Data as JSON: /api/errors/486c45bb2beb6f79.
Report an issue: GitHub.
Appendix: source
Thrown at crates/codex-plus-core/src/codex_home.rs:36
/// `%USERPROFILE%\.codex`(454 MB 会话历史)被永久删除,只剩 1.1% 可恢复。我审计了
/// 全部 `remove_dir_all` 调用点,没有找到一条能删到 `.codex` 的既定路径——也就是说
/// 现有代码在正常输入下是安全的。但这也意味着:**一旦某个上游值(环境变量、被解析
/// 坏的路径、更新后失效的目录)指向了 home 本身,就没有任何东西拦得住它。**
/// 数据丢失不可逆,所以这里加一道与具体触发源无关的兜底。
///
/// 判定在**规范化之后**做,`..`、符号链接、大小写差异都拦得住;同时容忍路径尚不存在
/// (清理临时目录的常见情形,此时用词法规范化比较)。
pub fn ensure_safe_recursive_removal(target: &Path, codex_home: &Path) -> anyhow::Result<()> {
let target = normalize_for_comparison(target);
// 根路径 = 有根前缀且没有父目录,覆盖 POSIX 根(`/`)与 Windows 的各种写法
// (`C:\`、`\\?\C:\`、UNC `\\server\share\`)。
//
// 不能只与 `Path::new("/")` 比较:Windows 上 `/` 不是绝对路径,会被 normalize
// 成当前盘符根(如 `C:\`),相等比较拦不住它——也就是说递归删除盘符根本可以
// 绕过这道守卫。`has_root()` 这一半也不可省:没有它 `C:` 会被误判成根。
if target.as_os_str().is_empty() || is_filesystem_root(&target) {
anyhow::bail!("拒绝删除文件系统根目录:{}", target.display());
}
let home = normalize_for_comparison(codex_home);
if target == home {
anyhow::bail!(
"拒绝递归删除 CODEX_HOME 本身({})——这会连同全部会话历史一起丢失",
target.display()
);
}
if home.starts_with(&target) {
anyhow::bail!(
"拒绝删除 CODEX_HOME 的祖先目录 {}(CODEX_HOME = {})",
target.display(),
home.display()
);
}
Ok(())
}
View on GitHub (pinned to b1ed92e5e4)