{"record":{"id":"2d5fb1fa2e047d76","repo":"BigPizzaV3/CodexPlusPlus","slug":"error-to-string","errorCode":null,"errorMessage":"error.to_string()","messagePattern":"error\\.to_string\\(\\)","errorType":"exception","errorClass":"std::io::Error (PermissionDenied)","httpStatus":null,"severity":"critical","filePath":"crates/codex-plus-core/src/install/mod.rs","lineNumber":132,"sourceCode":"    windows::build_windows_entrypoint_plan(options)\n}\n\npub fn build_macos_app_bundle(options: &InstallOptions, manager: bool) -> MacosAppBundle {\n    macos::build_app_bundle(options, manager)\n}\n\npub fn remove_owned_data() -> std::io::Result<()> {\n    let dir = crate::paths::default_app_state_dir();\n    if !dir.exists() {\n        return Ok(());\n    }\n    // 卸载流程会递归删除，路径来自环境/推导，先过一道\"不许删 CODEX_HOME 及其祖先\"\n    // 的兜底（#2146）。守卫只在这条路径确实指向 home 时才会拒绝，正常卸载不受影响。\n    if let Err(error) = crate::codex_home::ensure_safe_recursive_removal(\n        &dir,\n        &crate::codex_home::default_codex_home_dir(),\n    ) {\n        return Err(std::io::Error::new(\n            std::io::ErrorKind::PermissionDenied,\n            error.to_string(),\n        ));\n    }\n    std::fs::remove_dir_all(dir)?;\n    Ok(())\n}\n\npub fn default_install_root() -> Option<PathBuf> {\n    #[cfg(windows)]\n    {\n        return crate::windows_integration::desktop_dir().or_else(|| {\n            directories::UserDirs::new().and_then(|dirs| dirs.desktop_dir().map(PathBuf::from))\n        });\n    }\n\n    #[cfg(target_os = \"macos\")]\n    {","sourceCodeStart":114,"sourceCodeEnd":150,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6/crates/codex-plus-core/src/install/mod.rs#L114-L150","documentation":"In remove_owned_data, before recursively deleting the uninstall directory, the code guards against removing CODEX_HOME or any of its ancestors (issue #2146). If ensure_safe_recursive_removal rejects the path, the guard's error text is wrapped into an io::Error with PermissionDenied kind via error.to_string().","triggerScenarios":"Calling remove_owned_data with a dir that equals or is a parent/ancestor of the default codex home directory (e.g. the user's home dir, or CODEX_HOME itself), causing the safety guard to refuse the recursive delete.","commonSituations":"Uninstaller configured with the wrong install dir (empty or '/'-like path that is an ancestor of CODEX_HOME); environment misconfiguration where CODEX_HOME resolves to a broad directory; passing the wrong variable to the uninstall routine.","solutions":["Pass the actual owned data directory (the app's install/data dir), not CODEX_HOME or an ancestor","Check the CODEX_HOME environment variable / default_codex_home_dir() to confirm which paths are protected","Log the guard error to see exactly which path relationship triggered the refusal","If the target legitimately contains home but is not the home itself, delete subdirectories individually instead of the ancestor"],"exampleFix":"// before\nremove_owned_data(&std::env::var(\"HOME\").unwrap()); // resolves to ancestor of CODEX_HOME\n// after\nlet data_dir = install_root.join(\"owned-data\");\nremove_owned_data(&data_dir)?;","handlingStrategy":"validation","validationCode":"const home = process.env.CODEX_HOME || defaultCodexHome();\nconst rel = path.relative(home, targetDir);\nif (rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel))) {\n  throw new Error('refusing to delete CODEX_HOME or its ancestor');\n}","typeGuard":"const isSafeDeleteTarget = (target: string, home: string): boolean =>\n  path.relative(home, path.resolve(target)).startsWith('..');","tryCatchPattern":"if let Err(e) = remove_owned_data(&dir) {\n    if e.kind() == std::io::ErrorKind::PermissionDenied {\n        eprintln!(\"delete refused by safety guard: {e}\");\n        // do not retry; fix the target path\n    }\n}","preventionTips":["Always pass the app's owned data dir, never CODEX_HOME or its ancestors","Verify resolved (canonical) paths, not env strings, before deleting","Test uninstall paths with a fake CODEX_HOME in CI"],"tags":["filesystem","safety-guard","uninstall","recursive-delete"],"backgroundTag":"path-traversal-blocked","analyzedSha":"b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6","analyzedAt":"2026-09-19T23:35:21.129Z","contentChangedAt":"2026-09-19T23:35:21.129Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}