{"record":{"id":"7c0d83e3bbbbc932","repo":"astrid-runtime/astrid","slug":"astrid-volume-path-has-no-file-name","errorCode":null,"errorMessage":"Astrid volume path has no file name","messagePattern":"Astrid volume path has no file name","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-storage/src/volume/hosted/open.rs","lineNumber":28,"sourceCode":"use parking_lot::{Mutex, MutexGuard};\n\nuse super::{ContainerState, HostedFileVolume, VOLUME_MAGIC, reclaim, recover};\n\ntype SharedOpenReclaimLock = Mutex<()>;\n\nstatic OPEN_LOCKS: OnceLock<RwLock<BTreeMap<PathBuf, Weak<SharedOpenReclaimLock>>>> =\n    OnceLock::new();\n\n/// Process-local serialization shared by open and physical reclaim for one\n/// canonical parent plus final path component.\npub(super) struct OpenReclaimLock(Arc<SharedOpenReclaimLock>);\n\nimpl OpenReclaimLock {\n    fn for_path(path: &Path) -> io::Result<Self> {\n        let parent = path.parent().unwrap_or(Path::new(\"/\"));\n        let canonical_parent = std::fs::canonicalize(parent)?;\n        let file_name = path.file_name().ok_or_else(|| {\n            io::Error::new(\n                io::ErrorKind::InvalidInput,\n                \"Astrid volume path has no file name\",\n            )\n        })?;\n        let key = canonical_parent.join(file_name);\n        let registry = OPEN_LOCKS.get_or_init(|| RwLock::new(BTreeMap::new()));\n        if let Some(lock) = registry\n            .read()\n            .expect(\"Astrid volume open-lock registry\")\n            .get(&key)\n            .and_then(Weak::upgrade)\n        {\n            return Ok(Self(lock));\n        }\n        let mut guards = registry.write().expect(\"Astrid volume open-lock registry\");\n        if let Some(lock) = guards.get(&key).and_then(Weak::upgrade) {\n            return Ok(Self(lock));\n        }","sourceCodeStart":10,"sourceCodeEnd":46,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-storage/src/volume/hosted/open.rs#L10-L46","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Pass the full path to the volume file, including its file name (e.g. /data/volumes/astrid.vol).","Canonicalize or validate the configured path before opening and reject directory-only paths early.","Check the config value/env var supplying the path for empty or root values."],"exampleFix":"// before\nlet path = Path::new(&config.volume_dir); // directory, no file name\n// after\nlet path = config.volume_dir.join(\"volume.astrid\");\nassert!(path.file_name().is_some());","handlingStrategy":"validation","validationCode":"fn has_file_name(path: &Path) -> bool {\n    path.file_name().is_some()\n}","typeGuard":null,"tryCatchPattern":"match HostedVolume::open(&path) {\n    Err(e) if e.to_string().contains(\"no file name\") => {\n        eprintln!(\"configured volume path {path:?} lacks a file name component\");\n    }\n    other => { /* ... */ }\n}","preventionTips":["Validate that the configured path's file_name() is Some before opening.","Store full file paths (not directories) in configuration for volume locations.","Beware string operations (trimming, parent()) that strip the final component."],"tags":["storage","path","configuration","locking"],"backgroundTag":"invalid-url-format","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}