astrid-runtime/astrid · error · io::Error

layout record has no parent

Error message

layout record has no parent

What it means

atomic_write derives the parent directory of the target path via Path::parent(); if the path has no parent component (e.g. a bare relative file name like "record.json"), it raises InvalidInput "layout record has no parent" instead of writing into an ambiguous location. The library requires an explicit directory for durable atomic layout-record writes (it must create the parent and fsync it).

Source

Thrown at crates/astrid-core/src/dirs_layout.rs:518

fn ensure_migration_capacity(_target: &Path, _source_bytes: u64) -> io::Result<()> {
    Err(io::Error::new(
        io::ErrorKind::Unsupported,
        "layout migration capacity probing is unavailable in a WebAssembly guest",
    ))
}

fn atomic_write(path: &Path, bytes: &[u8]) -> io::Result<()> {
    #[cfg(windows)]
    {
        crate::platform_fs::atomic_write_private_file(path, bytes)
    }

    #[cfg(unix)]
    {
        use std::os::unix::fs::OpenOptionsExt as _;

        let parent = path.parent().ok_or_else(|| {
            io::Error::new(io::ErrorKind::InvalidInput, "layout record has no parent")
        })?;
        std::fs::create_dir_all(parent)?;
        let name = path
            .file_name()
            .and_then(|name| name.to_str())
            .ok_or_else(|| {
                io::Error::new(
                    io::ErrorKind::InvalidInput,
                    "layout record has no file name",
                )
            })?;
        let staged = parent.join(format!(".{name}.next"));
        match std::fs::symlink_metadata(&staged) {
            Ok(metadata) if metadata.file_type().is_file() => std::fs::remove_file(&staged)?,
            Ok(_) => {
                return Err(io::Error::new(
                    io::ErrorKind::InvalidData,
                    format!("layout staging path is redirected: {}", staged.display()),

View on GitHub (pinned to affd8760f4)

Solutions

  1. Pass an absolute or otherwise parent-bearing path, e.g. Path::new(".").join("version.json") or the layout directory joined with the file name.
  2. Verify the path with path.parent().is_some() before calling the API.
  3. Fix the caller/config that supplies the layout record path so it always includes the directory.

Example fix

// before
write_layout_version(Path::new("layout.json"), &record)?;
// after
let path = layout_dir.join("layout.json");
write_layout_version(&path, &record)?;
Defensive patterns

Strategy: validation

Validate before calling

fn has_parent(path: &Path) -> bool { path.parent().is_some() }
if !has_parent(record_path) { return Err("record path needs a directory component"); }

Type guard

fn writable_layout_path(path: &Path) -> Option<&Path> {
    path.parent().map(|_| path)
}

Try / catch

if let Err(e) = write_layout_version(path, record) {
    if e.kind() == io::ErrorKind::InvalidInput && e.to_string().contains("no parent") {
        eprintln!("supply a path with a directory component: {}", path.display());
    }
}

Prevention

When it happens

Trigger: Calling write_layout_version with a path such as Path::new("version.json") or any path whose parent() returns None (root-less relative name), so the ok_or_else branch fires in the unix block.

Common situations: Passing a bare file name from config instead of a full path; constructing the path by joining onto an empty base; tests or scripts that chdir and pass just a file name.

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/6d6be221813579a0. Report an issue: GitHub.