{"record":{"id":"b9fde14219e1531b","repo":"clockworklabs/SpacetimeDB","slug":"cannot-replace-without-enclosing-directory","errorCode":null,"errorMessage":"cannot replace {} without enclosing directory","messagePattern":"cannot replace (.+?) without enclosing directory","errorType":"exception","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"crates/paths/src/utils.rs","lineNumber":111,"sourceCode":"        }\n    };\n    ($(#[$($attr:tt)+])* $name:ident: file) => {\n        path_type!($(#[$($attr)+])* $name);\n        impl $name {\n            pub fn read(&self) -> std::io::Result<Vec<u8>> {\n                std::fs::read(self)\n            }\n\n            pub fn read_to_string(&self) -> std::io::Result<String> {\n                std::fs::read_to_string(self)\n            }\n\n            pub fn write(&self, contents: impl AsRef<[u8]>) -> std::io::Result<()> {\n                use std::io::Write as _;\n\n                let path = &self.0;\n                let parent = path.parent().ok_or_else(||\n                    std::io::Error::new(\n                        std::io::ErrorKind::InvalidInput,\n                        format!(\"cannot replace {} without enclosing directory\", path.display()))\n                )?;\n                std::fs::create_dir_all(&parent)?;\n\n                let mut tmp = $crate::__tempfile::NamedTempFile::new_in(parent)?;\n                tmp.write_all(contents.as_ref())?;\n                tmp.as_file().sync_all()?;\n                tmp.persist(&path)?;\n                // On Windows, syncing the directory is not necessary and doesn't even work.\n                #[cfg(not(target_os = \"windows\"))]\n                std::fs::File::open(parent)?.sync_all()?;\n\n                Ok(())\n            }\n\n            /// Opens a file at this path with the given options, ensuring its parent directory exists.\n            #[inline]","sourceCodeStart":93,"sourceCodeEnd":129,"githubUrl":"https://github.com/clockworklabs/SpacetimeDB/blob/524b4487d949b61a07d4f39c862d1290259dfd20/crates/paths/src/utils.rs#L93-L129","documentation":"The paths crate's macro-generated atomic write helper stages a NamedTempFile in the target's parent directory and persists it over the destination. Path::parent() returned None, which in Rust happens only for filesystem roots such as / (or a Windows drive root): there is no enclosing directory to stage the temp file in, so the write is rejected with InvalidInput before touching the filesystem.","triggerScenarios":"A path that normalizes to the filesystem root - PathBuf::from(\"/\"), joining onto a misconfigured empty/root base, or collecting an empty component iterator - passed to the generated write method.","commonSituations":"Config or argument parsing where an empty prefix, tenant, or component concatenates into a root path; user-supplied path strings used unvalidated; scripts accidentally writing to /.","solutions":["Fix the base-path construction so an empty or root prefix can never produce a root path.","Validate that the target path has a real parent component and is not a root before writing.","Reject empty/root paths at the API boundary with a clear error message."],"exampleFix":"// before: prefix from config can resolve to the root\nlet path = PathBuf::from(&prefix).join(&name);\npath.write(contents)?; // InvalidInput when path == \"/\"\n\n// after: reject root paths explicitly\nlet path = PathBuf::from(&prefix).join(&name);\nif path.parent().is_none() {\n    return Err(format!(\"refusing to write root path {}\", path.display()));\n}\npath.write(contents)?;","handlingStrategy":"type-guard","validationCode":"use std::path::Path;\n\nfn ensure_writable(path: &Path) -> Result<(), String> {\n    match path.parent() {\n        Some(p) if !p.as_os_str().is_empty() => Ok(()),\n        _ => Err(format!(\"path {} has no enclosing directory\", path.display())),\n    }\n}","typeGuard":"fn is_root_path(p: &std::path::Path) -> bool {\n    p.parent().is_none()\n}","tryCatchPattern":"match path.write(contents) {\n    Err(e) if e.kind() == std::io::ErrorKind::InvalidInput => {\n        // Path construction bug: log the offending path and fix the base/prefix logic.\n    }\n    r => r,\n}","preventionTips":["Validate user- or config-supplied paths at the API boundary before writing.","Unit-test path-building code with empty prefixes and root bases.","Treat root paths as programmer error and reject them loudly."],"tags":["filesystem","path","validation","rust"],"backgroundTag":"invalid-file-path","analyzedSha":"524b4487d949b61a07d4f39c862d1290259dfd20","analyzedAt":"2026-08-16T23:58:54.611Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}