{"id":"9cf9140e251f5061","repo":"rust-lang/cargo","slug":"lockfile-path-can-t-be-root","errorCode":null,"errorMessage":"Lockfile path can't be root","messagePattern":"Lockfile path can't be root","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/workspace/workspace.rs","lineNumber":733,"sourceCode":"        self\n    }\n\n    pub fn ignore_lock(&self) -> bool {\n        self.ignore_lock\n    }\n\n    pub fn set_ignore_lock(&mut self, ignore_lock: bool) -> &mut Workspace<'gctx> {\n        self.ignore_lock = ignore_lock;\n        self\n    }\n\n    /// Returns the directory where the lockfile is in.\n    pub fn lock_root(&self) -> Filesystem {\n        if let Some(requested) = self.requested_lockfile_path.as_ref() {\n            return Filesystem::new(\n                requested\n                    .parent()\n                    .expect(\"Lockfile path can't be root\")\n                    .to_owned(),\n            );\n        }\n        self.default_lock_root()\n    }\n\n    fn default_lock_root(&self) -> Filesystem {\n        if self.root_maybe().is_embedded() {\n            // Include a workspace hash in case the user requests a shared build-dir so that\n            // scripts don't fight over the `Cargo.lock` content\n            let workspace_manifest_path = self.root_manifest();\n            let real_path = std::fs::canonicalize(workspace_manifest_path)\n                .unwrap_or_else(|_err| workspace_manifest_path.to_owned());\n            let hash = crate::util::hex::short_hash(&real_path);\n            self.build_dir().join(hash)\n        } else {\n            Filesystem::new(self.root().to_owned())\n        }","sourceCodeStart":715,"sourceCodeEnd":751,"githubUrl":"https://github.com/rust-lang/cargo/blob/0e07a155371a6ce88ae53a2c00df940280c09a67/src/workspace/workspace.rs#L715-L751","documentation":"Panic in Workspace::lock_root (src/workspace/workspace.rs:733). When a custom lockfile location is set (via the resolver.lockfile-path config key or the deprecated --lockfile-path flag), lock_root() returns the directory containing the lockfile by calling requested.parent().expect(\"Lockfile path can't be root\"). Path::parent() returns None when the path is a filesystem root (e.g. \"/\", \"C:\\\", or a UNC root), so the panic fires when the configured/derived lockfile path resolves to a root that has no parent directory.","triggerScenarios":"Setting resolver.lockfile-path in .cargo/config.toml (or passing --lockfile-path) to a value that, after ConfigRelativePath resolution and the ends_with(\"Cargo.lock\") + non-directory checks at lines 395-403, still ends up at a path whose parent() is None - e.g. a root-level absolute path or a degenerate Windows prefix path. The existing validation does NOT reject parent()==None, so such a value slips through to lock_root() and panics on the next resolution.","commonSituations":"Trying to force the lockfile to a root-mounted location; a misbehaving wrapper/script that computes lockfile-path and accidentally yields a root; Windows path quirks where a prefix-only path has no directory parent; copy-pasting an absolute lockfile path that normalizes to root after config-relative resolution.","solutions":["Point resolver.lockfile-path at a real subdirectory, not a filesystem root: use a relative path (relative to the config file) or an absolute path under an existing directory, e.g. `lockfile-path = \"target/Cargo.lock\"`.","Before relying on the value, verify the resolved path has a parent: in tooling that drives cargo, assert path.parent().is_some() and that the parent is an existing writable directory.","If you do not actually need a custom lockfile location, remove the resolver.lockfile-path key (and the --lockfile-path flag) entirely so cargo falls back to default_lock_root().","On Windows, double-check the path is a fully-qualified `C:\\dir\\Cargo.lock` form, not a prefix-only or UNC-root value."],"exampleFix":"# before (panics):\n#   # .cargo/config.toml\n#   [resolver]\n#   lockfile-path = \"/Cargo.lock\"     # parent() degenerate => panic in lock_root\n\n# after (safe):\n#   [resolver]\n#   lockfile-path = \"target/Cargo.lock\"   # real parent dir => lock_root() ok","handlingStrategy":"validation","validationCode":"use std::path::Path;\n\n// Validate the configured lockfile path BEFORE letting cargo call lock_root().\n// Mirrors the in-cargo checks (ends_with Cargo.lock, not a dir) and adds the missing parent() guard.\nfn valid_lockfile_path(p: &Path) -> Result<(), String> {\n    if !p.ends_with(\"Cargo.lock\") {\n        return Err(\"lockfile-path must end with Cargo.lock\".into());\n    }\n    let parent = p.parent().ok_or_else(|| \"lockfile-path resolves to a filesystem root (no parent)\".to_string())?;\n    if parent.as_os_str().is_empty() {\n        return Err(\"lockfile-path needs an explicit parent directory\".into());\n    }\n    if p.is_dir() {\n        return Err(\"lockfile-path is a directory\".into());\n    }\n    Ok(())\n}\n","typeGuard":"use std::path::{Path, PathBuf};\n\n// Narrow to a path guaranteed not to trip lock_root()'s .expect.\nstruct NonRootLockfilePath(PathBuf);\n\nimpl NonRootLockfilePath {\n    fn new(p: PathBuf) -> Result<Self, &'static str> {\n        match (p.ends_with(\"Cargo.lock\"), p.parent()) {\n            (true, Some(parent)) if !parent.as_os_str().is_empty() && !p.is_dir() => Ok(Self(p)),\n            _ => Err(\"lockfile-path must be a Cargo.lock inside a real directory\"),\n        }\n    }\n    fn as_path(&self) -> &Path { &self.0 }\n}\n","tryCatchPattern":"// Not recommended: this is a deterministic config error, not a runtime fault.\n// Prefer validation (above). If you must defend at the boundary:\nuse std::panic;\nlet root = panic::catch_unwind(|| workspace.lock_root());\nmatch root {\n    Ok(fs) => { /* use fs */ }\n    Err(_)  => { /* fall back to ws.set_requested_lockfile_path(None) and use default_lock_root() */ }\n}","preventionTips":["Use a relative resolver.lockfile-path (resolved against the config file) so it can never become a filesystem root.","Add a parent()-is_some() + non-empty check in any wrapper that computes a lockfile path before passing it to cargo.","Omit resolver.lockfile-path / --lockfile-path unless you genuinely need a custom lockfile location.","On Windows, always use a fully-qualified `DRIVE:\\dir\\Cargo.lock` form."],"tags":["lockfile","config","paths","filesystem","workspace","rust"],"analyzedSha":"0e07a155371a6ce88ae53a2c00df940280c09a67","analyzedAt":"2026-08-06T01:46:58.334Z","schemaVersion":2}