{"record":{"id":"ee3c6dedbd241c22","repo":"astrid-runtime/astrid","slug":"lazy-unmount-error","errorCode":null,"errorMessage":"lazy unmount {}: {error}","messagePattern":"lazy unmount (.+?): (.+?)","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-storage-provider-fuse/src/mountpoint.rs","lineNumber":80,"sourceCode":"    }\n    Ok((canonical, !existed))\n}\n\n/// Return current owner identity for synthetic inode metadata.\npub(crate) fn owner_ids() -> (u32, u32) {\n    (getuid().into(), getgid().into())\n}\n\n/// Remove a dead FUSE mount if one remains at an exact canonical path.\npub(crate) fn lazy_unmount(mountpoint: &Path) -> Result<()> {\n    use nix::mount::MntFlags;\n\n    if !mountpoint.is_absolute() {\n        bail!(\"cannot unmount a relative mountpoint\");\n    }\n    match nix::mount::umount2(mountpoint, MntFlags::MNT_DETACH) {\n        Ok(()) | Err(nix::errno::Errno::EINVAL) => Ok(()),\n        Err(error) => Err(anyhow::anyhow!(\n            \"lazy unmount {}: {error}\",\n            mountpoint.display()\n        )),\n    }\n}\n\n/// Whether `/proc/self/mountinfo` has a mount at this exact path.\npub(crate) fn mountinfo_contains(mountpoint: &Path) -> Result<bool> {\n    let expected = mountpoint\n        .to_str()\n        .context(\"mountpoint must be canonical Unicode text\")?\n        .as_bytes();\n    let mounts = std::fs::read(\"/proc/self/mountinfo\").context(\"read Linux mount table\")?;\n    Ok(mounts\n        .split(|byte| *byte == b'\\n')\n        .filter(|line| !line.is_empty())\n        .any(|line| {\n            line.split(|byte| *byte == b' ')","sourceCodeStart":62,"sourceCodeEnd":98,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-storage-provider-fuse/src/mountpoint.rs#L62-L98","documentation":"lazy_unmount performs MNT_DETACH on the given path via nix::mount::umount2. If the kernel returns an errno other than success or EINVAL (already-unmounted, treated as ok), the error is wrapped as 'lazy unmount <path>: <errno>'.","triggerScenarios":"Calling lazy_unmount on a path that is not a mountpoint (ENOENT), is owned by another user without privileges (EPERM), or is in a state where detach is refused (EBUSY in restrictive configs).","commonSituations":"Mountpoint was already unmounted by another process or crashed daemon; running without root/CAP_SYS_ADMIN in a container; the registry record points to a deleted path; typo'd or stale mountpoint path.","solutions":["Check the wrapped errno: EPERM means run as root / with CAP_SYS_ADMIN","If ENOENT, the mountpoint no longer exists — treat the record as already cleaned and remove it from the registry","Verify the path in the error message is a real, currently mounted FUSE mountpoint (mount/findmnt)","Ensure the mountpoint path is absolute; relative paths are rejected earlier"],"exampleFix":"// before: any errno surfaces as failure\nmatch nix::mount::umount2(mountpoint, MntFlags::MNT_DETACH) {\n    Ok(()) | Err(nix::errno::Errno::EINVAL) => Ok(()),\n// after: also tolerate already-gone mountpoints\n    Ok(()) | Err(nix::errno::Errno::EINVAL) | Err(nix::errno::Errno::ENOENT) => Ok(()),","handlingStrategy":"validation","validationCode":"// before calling unmount\nif !mountpoint.is_absolute() { bail!(\"relative path\"); }\nif !Path::new(\"/proc/mounts\").to_path_buf().exists() ||\n   !std::fs::read_to_string(\"/proc/mounts\")?.contains(mountpoint.as_os_str()) {\n    // not mounted — skip umount2\n}","typeGuard":null,"tryCatchPattern":"match result {\n    Ok(()) => (),\n    Err(e) if e.to_string().contains(\"EPERM\") => bail!(\"need root/CAP_SYS_ADMIN\"),\n    Err(e) if e.to_string().contains(\"ENOENT\") => (), // already gone\n    Err(e) => return Err(e),\n}","preventionTips":["Check /proc/mounts or findmnt before unmounting","Run unmounts with sufficient privileges (root/CAP_SYS_ADMIN)","Prune registry records whose mountpoints no longer exist","Always pass absolute paths"],"tags":["fuse","unmount","filesystem","linux"],"backgroundTag":"permission-denied","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}