astrid-runtime/astrid · error

Astrid volume path has no file name

Error message

Astrid volume path has no file name

What it means

OpenReclaimLock::for_path builds a registry key from the path's canonical parent directory plus its file-name component. If the path has no final component (e.g. it is "/", "..", or an empty/root path), file_name() returns None and the lock cannot be keyed, so InvalidInput is raised.

Solutions

  1. Pass the full path to the volume file, including its file name (e.g. /data/volumes/astrid.vol).
  2. Canonicalize or validate the configured path before opening and reject directory-only paths early.
  3. Check the config value/env var supplying the path for empty or root values.

Example fix

// before
let path = Path::new(&config.volume_dir); // directory, no file name
// after
let path = config.volume_dir.join("volume.astrid");
assert!(path.file_name().is_some());
Defensive patterns

Strategy: validation

Validate before calling

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

Try / catch

match HostedVolume::open(&path) {
    Err(e) if e.to_string().contains("no file name") => {
        eprintln!("configured volume path {path:?} lacks a file name component");
    }
    other => { /* ... */ }
}

Prevention

When it happens

Trigger: Passing a Path with no file name to the volume-open path — e.g. Path::new("/"), Path::new("."), Path::new(".."), or a path that is only a directory prefix ending in a separator with nothing after it.

Common situations: Config/env where the volume path variable is empty or set to a mount root; string manipulation that stripped the file name (e.g. trimming the last component); passing a directory instead of a volume file path.

Understand the failure class

Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.

Related errors


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

Appendix: source

Thrown at crates/astrid-storage/src/volume/hosted/open.rs:28

use parking_lot::{Mutex, MutexGuard};

use super::{ContainerState, HostedFileVolume, VOLUME_MAGIC, reclaim, recover};

type SharedOpenReclaimLock = Mutex<()>;

static OPEN_LOCKS: OnceLock<RwLock<BTreeMap<PathBuf, Weak<SharedOpenReclaimLock>>>> =
    OnceLock::new();

/// Process-local serialization shared by open and physical reclaim for one
/// canonical parent plus final path component.
pub(super) struct OpenReclaimLock(Arc<SharedOpenReclaimLock>);

impl OpenReclaimLock {
    fn for_path(path: &Path) -> io::Result<Self> {
        let parent = path.parent().unwrap_or(Path::new("/"));
        let canonical_parent = std::fs::canonicalize(parent)?;
        let file_name = path.file_name().ok_or_else(|| {
            io::Error::new(
                io::ErrorKind::InvalidInput,
                "Astrid volume path has no file name",
            )
        })?;
        let key = canonical_parent.join(file_name);
        let registry = OPEN_LOCKS.get_or_init(|| RwLock::new(BTreeMap::new()));
        if let Some(lock) = registry
            .read()
            .expect("Astrid volume open-lock registry")
            .get(&key)
            .and_then(Weak::upgrade)
        {
            return Ok(Self(lock));
        }
        let mut guards = registry.write().expect("Astrid volume open-lock registry");
        if let Some(lock) = guards.get(&key).and_then(Weak::upgrade) {
            return Ok(Self(lock));
        }

View on GitHub (pinned to affd8760f4)