astrid-runtime/astrid · error

groups path has no parent directory

Error message

groups path has no parent directory

What it means

write_atomic in the groups config module needs the parent directory of the target path to place its temporary file before renaming; if path.parent() returns None there is no parent to write into. Astrid raises GroupConfigError::Io with io::ErrorKind::InvalidInput for this case. In practice this only happens when the path has no parent component, i.e. it is a bare root like `/` or an empty/degenerate path.

Source

Thrown at crates/astrid-core/src/groups/io_impl.rs:77

            }
            custom.insert(name.clone(), group.clone());
        }
        let file = GroupsFileOwned { groups: custom };
        let content = toml::to_string_pretty(&file).map_err(|e| {
            GroupConfigError::Io(io::Error::other(format!(
                "failed to serialize groups.toml: {e}"
            )))
        })?;
        write_atomic(path, content.as_bytes())
    }
}

#[cfg(unix)]
static TMP_COUNTER: AtomicU64 = AtomicU64::new(0);

fn write_atomic(path: &Path, data: &[u8]) -> GroupConfigResult<()> {
    let parent = path.parent().ok_or_else(|| {
        GroupConfigError::Io(io::Error::new(
            io::ErrorKind::InvalidInput,
            "groups path has no parent directory",
        ))
    })?;
    #[cfg(windows)]
    crate::platform_fs::ensure_private_directory(parent)?;
    #[cfg(not(windows))]
    fs::create_dir_all(parent)?;

    #[cfg(unix)]
    {
        use std::io::Write;
        use std::os::unix::fs::OpenOptionsExt;

        let seq = TMP_COUNTER.fetch_add(1, Ordering::Relaxed);
        let tmp_path = path.with_extension(format!("toml.tmp.{}.{seq}", std::process::id()));
        let mut f = fs::OpenOptions::new()
            .write(true)

View on GitHub (pinned to affd8760f4)

Solutions

  1. Fix the groups path so it names a file inside a real directory, e.g. ~/.astrid/groups.json instead of an empty or root path
  2. Check the config source that supplies the path for empty/unexpanded values and validate it before save
  3. If constructing the path programmatically, join a filename onto a directory (dir.join("groups.json")) so parent() is always Some

Example fix

// before
let path = PathBuf::from("");            // parent() == None
// after
let path = home.root().join("groups.json"); // parent == home root
Defensive patterns

Strategy: validation

Validate before calling

fn saveable_groups_path(p: &std::path::Path) -> bool {
    p.parent().is_some() && p.file_name().is_some()
}
assert!(saveable_groups_path(&groups_path), "groups path must name a file inside a directory");

Try / catch

match groups.save_to_path(&path) {
    Err(GroupConfigError::Io(e)) if e.kind() == std::io::ErrorKind::InvalidInput => {
        // path had no parent; reconstruct from a valid directory
    },
    other => other?,
}

Prevention

When it happens

Trigger: Calling save_to_path (groups config persistence) with a Path that is the filesystem root `/`, an empty path, or otherwise yields None from Path::parent().

Common situations: A configuration field holding the groups file path is empty or set to `/` due to a bad default, unexpanded variable, or truncated config value.

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/276a1e2050b0a1e7. Report an issue: GitHub.