{"record":{"id":"fe66bcc95362b56d","repo":"astrid-runtime/astrid","slug":"private-directory-has-no-unix-root","errorCode":null,"errorMessage":"private directory has no Unix root: {}","messagePattern":"private directory has no Unix root: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-core/src/platform_fs.rs","lineNumber":726,"sourceCode":"        return Err(io::Error::new(\n            io::ErrorKind::InvalidInput,\n            format!(\"private directory contains traversal: {}\", path.display()),\n        ));\n    }\n\n    let absolute = normalize_unix_system_alias(if path.is_absolute() {\n        path.to_path_buf()\n    } else {\n        std::env::current_dir()?.join(path)\n    });\n    let mut directory = if absolute\n        .components()\n        .next()\n        .is_some_and(|component| matches!(component, Component::RootDir))\n    {\n        std::fs::File::open(\"/\")\n    } else {\n        return Err(io::Error::new(\n            io::ErrorKind::InvalidInput,\n            format!(\"private directory has no Unix root: {}\", path.display()),\n        ));\n    }?;\n    let mut missing = Vec::new();\n    for component in absolute\n        .components()\n        .filter_map(|component| match component {\n            Component::Normal(name) => Some(name.to_os_string()),\n            _ => None,\n        })\n    {\n        if missing.is_empty() {\n            let flags = OFlag::O_RDONLY | OFlag::O_DIRECTORY | OFlag::O_NOFOLLOW | OFlag::O_CLOEXEC;\n            match openat(&directory, component.as_os_str(), flags, Mode::empty()) {\n                Ok(next) => directory = std::fs::File::from(next),\n                Err(Errno::ENOENT) => missing.push(component),\n                Err(error) => return Err(nix_io_error(error)),","sourceCodeStart":708,"sourceCodeEnd":744,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-core/src/platform_fs.rs#L708-L744","documentation":"This error is thrown by `unix_directory_walk` when a private-directory path, after normalization and resolution against the current working directory, does not start with a Unix root component (`/`). The function opens `/` as the starting directory handle for a no-follow openat walk; without a root it cannot anchor the walk, so it refuses with `InvalidInput`. It indicates the caller passed a path the library cannot represent as an absolute Unix directory path.","triggerScenarios":"Calling `ensure_private_directory_unix` or `open_directory_no_follow_unix` with a path that normalizes to nothing anchored at `/` — e.g. an empty path, a bare relative name like `foo` when `std::env::current_dir()` itself yields a non-rooted path, or a path composed only of `.`/`CurDir` components so the resulting absolute path has no `RootDir` component.","commonSituations":"Passing an empty string or `\".\"` as a private directory path; a corrupted `PWD`/current-dir environment so relative paths fail to anchor; tests or tools that construct `PathBuf` paths from fragments without joining to a rooted base.","solutions":["Pass an absolute path beginning with `/` (e.g. `Path::new(\"/home/user/.astrid\")`) instead of a relative or empty path","If starting from a relative path, join it onto a known rooted base such as `std::env::current_dir()` or `dirs::home_dir()` before calling","Check that the path contains at least one `Normal` component and starts with `Component::RootDir` before calling","Verify the process's current working directory is valid and rooted if relying on relative-path resolution"],"exampleFix":"// before\nensure_private_directory(Path::new(\".astrid/keys\"))?\n// after\nlet base = dirs::home_dir().context(\"no home dir\")?;\nensure_private_directory(&base.join(\".astrid/keys\"))?","handlingStrategy":"validation","validationCode":"fn is_rooted_unix_dir(path: &Path) -> bool {\n    path.is_absolute() && path.components().any(|c| matches!(c, std::path::Component::Normal(_)))\n}\n// call only if is_rooted_unix_dir(&private_dir)","typeGuard":"fn has_unix_root(path: &Path) -> bool {\n    matches!(path.components().next(), Some(std::path::Component::RootDir))\n}","tryCatchPattern":"match ensure_private_directory(&dir) {\n    Err(e) if e.kind() == std::io::ErrorKind::InvalidInput => eprintln!(\"not an absolute Unix path: {e}\"),\n    Err(e) => return Err(e.into()),\n    Ok(()) => {},\n}","preventionTips":["Always pass absolute paths starting with '/' to private-directory APIs","Anchor relative paths to home_dir() or current_dir() at construction time","Never pass empty or '.'-only paths","Validate with Path::is_absolute() before calling"],"tags":["filesystem","invalid-input","unix","path"],"backgroundTag":"invalid-argument-value","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}