denisidoro/navi · error · anyhow

`{}` has no parent

Error message

`{}` has no parent

What it means

`common::fs::follow_symlink` resolves symlink components. When a symlink's stored target is a relative path starting with '.', the code needs the symlink's own parent directory to re-anchor the relative path; `PathBuf::parent()` returning None (no parent component, e.g. a bare relative filename) triggers `anyhow!("`{}` has no parent", pathbuf.display())`.

Source

Thrown at src/common/fs.rs:69

    Ok(pathbuf
        .as_ref()
        .as_os_str()
        .to_str()
        .ok_or_else(|| InvalidPath(pathbuf.as_ref().to_path_buf()))
        .map(str::to_string)?)
}

fn follow_symlink(pathbuf: PathBuf) -> Result<PathBuf> {
    read_link(pathbuf.clone())
        .map(|o| {
            let o_str = o
                .as_os_str()
                .to_str()
                .ok_or_else(|| InvalidPath(o.to_path_buf()))?;
            if o_str.starts_with('.') {
                let p = pathbuf
                    .parent()
                    .ok_or_else(|| anyhow!("`{}` has no parent", pathbuf.display()))?;
                let mut p = PathBuf::from(p);
                p.push(o_str);
                follow_symlink(p)
            } else {
                follow_symlink(o)
            }
        })
        .unwrap_or(Ok(pathbuf))
}

fn exe_pathbuf() -> Result<PathBuf> {
    let pathbuf = current_exe().context("Unable to acquire executable's path")?;

    #[cfg(target_family = "windows")]
    let pathbuf = dunce::canonicalize(pathbuf)?;

    debug!(current_exe = ?pathbuf);
    follow_symlink(pathbuf)

View on GitHub (pinned to f7330b9ad5)

Solutions

  1. Pass an absolute path (or one with a parent component like ./navi) to follow_symlink/exe_pathbuf
  2. Canonicalize with std::fs::canonicalize before calling when possible
  3. Recreate the symlink with an absolute or properly relative (../-anchored) target
  4. Upstream: fall back to PathBuf::from(".") as parent instead of erroring

Example fix

// before
let p = PathBuf::from("navi");
fs::follow_symlink(p)?; // panic: `navi` has no parent
// after
let p = std::env::current_dir()?.join("navi");
fs::follow_symlink(p)?;
Defensive patterns

Strategy: validation

Validate before calling

fn has_parent(p: &std::path::Path) -> bool {
    p.parent().is_some()
}
let path = std::path::PathBuf::from("navi");
if !has_parent(&path) {
    let path = std::env::current_dir()?.join(&path); // anchor to cwd first
}
fs::follow_symlink(&path)?;

Type guard

fn is_resolvable_symlink_path(p: &std::path::Path) -> bool {
    p.is_absolute() || p.parent().is_some()
}

Try / catch

match fs::follow_symlink(&path) {
    Ok(resolved) => use_resolved(resolved),
    Err(e) if e.to_string().contains("has no parent") => {
        let anchored = std::env::current_dir()?.join(&path);
        use_resolved(fs::follow_symlink(&anchored)?);
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: `follow_symlink(path)` (recursively, or via `exe_pathbuf`) where `path` is a relative path with no parent directory component (e.g. "navi") and the file is a symlink whose target begins with '.'.

Common situations: Inspecting executables/symlinks installed with bare relative names in the current directory, broken or unusually-constructed relative symlinks, or programmatically passing a single-component path to exe_pathbuf.

Related errors


AI-assisted analysis of denisidoro/navi@f7330b9ad5 (2026-09-03). Data as JSON: /api/errors/29a56460e8f96f93. Report an issue: GitHub.