astrid-runtime/astrid · error

private file has no name

Error message

private file has no name: {}

What it means

Alongside a parent, open_file_no_follow_unix requires a final file name component to perform the openat call. It throws this when path.file_name() returns None — paths that end in "..", ".", or are root — because there is no concrete file to open.

Solutions

  1. Pass the exact file path, not a directory or a path ending in ".." or ".".
  2. Normalize the path before calling (e.g. path.canonicalize() then append the file name).
  3. Add a caller-side check: `path.file_name().is_some()` before invoking the API.
  4. Fix any string-concatenation code that appends separators or ".." fragments.

Example fix

// before
restrict_private_file(Path::new("/home/me/.astrid/.."))?;
// after
restrict_private_file(Path::new("/home/me/.astrid/credentials"))?;
Defensive patterns

Strategy: validation

Validate before calling

fn names_a_file(path: &std::path::Path) -> bool {
    path.file_name().is_some()
        && !matches!(path.components().next_back(), Some(std::path::Component::ParentDir) | Some(std::path::Component::CurDir))
}

Type guard

fn has_file_name(path: &std::path::Path) -> bool { path.file_name().is_some() }

Try / catch

if path.file_name().is_none() {
    return Err(anyhow!("path must end in a file name: {}", path.display()));
}
validate_private_file(path)?;

Prevention

When it happens

Trigger: Calling restrict_private_file or validate_private_file with a path ending in "..", ".", or a root path, so file_name() yields None.

Common situations: Joining or normalizing logic that produced "dir/.."; a caller passing the parent directory instead of the file; paths built by string concatenation ending with a trailing separator that normalizes to ".".

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09). Data as JSON: /api/errors/c9261503ea045260. Report an issue: GitHub.

Appendix: source

Thrown at crates/astrid-core/src/platform_fs.rs:642

        Ok(path.to_path_buf())
    } else {
        Ok(std::env::current_dir()?.join(path))
    }
}

#[cfg(unix)]
fn open_file_no_follow_unix(path: &Path) -> io::Result<std::fs::File> {
    use nix::fcntl::{OFlag, openat};
    use nix::sys::stat::Mode;

    let parent = path.parent().ok_or_else(|| {
        io::Error::new(
            io::ErrorKind::InvalidInput,
            format!("private file has no parent: {}", path.display()),
        )
    })?;
    let name = path.file_name().ok_or_else(|| {
        io::Error::new(
            io::ErrorKind::InvalidInput,
            format!("private file has no name: {}", path.display()),
        )
    })?;
    let directory = open_directory_no_follow_unix(parent)?;
    let flags = OFlag::O_RDONLY | OFlag::O_NOFOLLOW | OFlag::O_CLOEXEC;
    openat(&directory, name, flags, Mode::from_bits_truncate(0o600))
        .map(std::fs::File::from)
        .map_err(nix_io_error)
}

#[cfg(unix)]
fn atomic_write_private_file_unix(path: &Path, bytes: &[u8]) -> io::Result<()> {
    use std::io::Write as _;
    use std::os::unix::fs::OpenOptionsExt as _;

    let parent = path.parent().ok_or_else(|| {
        io::Error::new(

View on GitHub (pinned to affd8760f4)