astrid-runtime/astrid · error · io::Error
layout record has no file name
Error message
layout record has no file name
What it means
atomic_write needs the file name component of the target path to build its staging file (.{name}.next). When path.file_name() is None or is not valid UTF-8 (e.g. the path is "..", ends in "..", or contains non-UTF-8 bytes on unix), it raises InvalidInput "layout record has no file name". This guards against silently mis-staging the atomic write.
Source
Thrown at crates/astrid-core/src/dirs_layout.rs:525
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()),
));
},
Err(error) if error.kind() == io::ErrorKind::NotFound => {},
Err(error) => return Err(error),
}
let mut file = OpenOptions::new()
.write(true)View on GitHub (pinned to affd8760f4)
Solutions
- Pass a path with a concrete UTF-8 file name, e.g. layout_dir.join("layout-v2.json").
- Check path.file_name().and_then(OsStr::to_str).is_some() before calling.
- Reject paths ending in ".." or "/" in your own path-building code.
- Convert non-UTF-8 sources to valid UTF-8 names when constructing the record path.
Example fix
// before
let path = layout_dir.join("..");
write_layout_version(&path, &record)?;
// after
let path = layout_dir.join("layout-v2.json");
write_layout_version(&path, &record)?; Defensive patterns
Strategy: validation
Validate before calling
fn has_valid_file_name(path: &Path) -> bool {
path.file_name().and_then(|n| n.to_str()).is_some()
} Type guard
fn utf8_file_name(path: &Path) -> Option<&str> {
path.file_name().and_then(|n| n.to_str())
} Try / catch
match write_layout_version(path, record) {
Err(e) if e.kind() == io::ErrorKind::InvalidInput && e.to_string().contains("no file name") => {
eprintln!("path must end in a concrete UTF-8 file name: {}", path.display());
},
other => other?,
} Prevention
- Never pass paths ending in "/", ".", or ".." to layout APIs
- Ensure file names are valid UTF-8 when sourced from raw OS bytes
- Assert file_name().is_some() in path-construction helpers
When it happens
Trigger: Calling write_layout_version with a path that is a directory root, "..", ends with a separator, or whose final component is non-UTF-8, so and_then(|name| name.to_str()) yields None.
Common situations: Passing a directory instead of a file path; paths built from raw OS bytes with non-UTF-8 components; accidentally joining an empty file-name onto the layout directory.
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
- layout record has no parent
- failed to persist {}: {e}
- layout staging path is redirected: {}
- durable layout records are unsupported on this operating sys
- private atomic-file backend is selected by Windows callers o
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/e92d4b9b36ed8e89.
Report an issue: GitHub.