xai-org/grok-build · error
unmount {}: {err}
Error message
unmount {}: {err} What it means
unmount_overlay lazily detaches a btrfs/mount overlay snapshot with umount2(MNT_DETACH). When the kernel returns a non-zero exit code, the raw OS error (last_os_error) is wrapped with the target path and bailed out. This means the kernel refused (or could not complete) the detach of the mount point at `target`.
Source
Thrown at crates/codegen/xai-fast-worktree/src/overlay/snapshot.rs:573
let err = std::io::Error::last_os_error();
bail!("mount overlay at {}: {err}", target.display());
}
Ok(())
}
/// Unmount a filesystem (lazy/detach to avoid EBUSY).
fn unmount_overlay(target: &Path) -> Result<()> {
use std::ffi::CString;
let c_target =
CString::new(target.as_os_str().as_encoded_bytes()).context("target path not C-safe")?;
// SAFETY: c_target is a valid CString.
let rc = unsafe { libc::umount2(c_target.as_ptr(), libc::MNT_DETACH) };
if rc != 0 {
let err = std::io::Error::last_os_error();
bail!("unmount {}: {err}", target.display());
}
Ok(())
}
/// Delete a btrfs subvolume/snapshot (delegates to shared btrfs module).
fn delete_btrfs_snapshot(path: &Path) -> Result<()> {
crate::btrfs::snapshot::delete_snapshot(path)
}
/// Write metadata JSON to the worktree base dir for crash recovery.
///
/// Written to `<wt_base>/.fast-worktree-meta.json` (next to `upper/` and
/// `work/` dirs), NOT inside the overlay. This ensures the metadata is
/// always readable from the btrfs filesystem regardless of overlay mount state.
fn write_metadata(
wt_base: &Path,
snapshot_root: &Path,View on GitHub (pinned to bc7f02eddd)
Solutions
- Check the target is actually a mount point (e.g. /proc/self/mounts or findmnt) before calling; skip/treat already-unmounted as success.
- Run as root or with CAP_SYS_ADMIN — umount2 requires privileges.
- Find and stop processes holding the mount open (fuser -vm <target> / lsof +f -- <target>) and retry.
- Since MNT_DETACH is lazy, a persistent EBUSY may need a plain retry loop or a stronger umount after closing holders.
Example fix
// before: unconditional unmount, fails on non-mount targets
unmount_overlay(&snapshot.target)?;
// after: skip targets that are not mounted
if is_mount_point(&snapshot.target) {
unmount_overlay(&snapshot.target)?;
} Defensive patterns
Strategy: validation
Validate before calling
fn is_mount_point(p: &Path) -> bool {
let st = std::fs::metadata(p).map(|m| m.dev()).ok();
let parent_st = p.parent().and_then(|p| std::fs::metadata(p).ok()).map(|m| m.dev());
match (st, parent_st) {
(Some(a), Some(b)) => a != b,
_ => false,
}
}
if !is_mount_point(&target) { skip_or_log(); } Try / catch
match unmount_overlay(&target) {
Err(e) if e.to_string().contains("Invalid argument") => {
tracing::debug!("already unmounted: {target:?}");
}
Err(e) => return Err(e.context(format!("unmount {target:?} failed"))),
Ok(()) => {}
} Prevention
- Run cleanup under root or a systemd unit with CAP_SYS_ADMIN
- Check findmnt before unmounting; treat 'not mounted' as success
- Close processes holding files in the snapshot (leaked fds, shell cwd)
- Retry once on EBUSY — MNT_DETACH may need a second pass
When it happens
Trigger: Calling cleanup_orphaned_overlay_snapshots -> unmount_overlay for a target path that is not currently a mount point (EINVAL/ENOENT), is busy (EBUSY because a process holds a file/dir open inside it), or requires privileges not available (EPERM when not root / lacking CAP_SYS_ADMIN).
Common situations: Running the cleanup as a non-root user; a leaked file descriptor or shell cd'd into the snapshot keeps it busy; the snapshot was already unmounted by another process (stale bookkeeping) so umount2 returns EINVAL; NFS/overlay races leaving the mount in a partially-detached state.
Related errors
- bind {}: {e}
- clear failed
- Failed to enable plugin: {e}
- Failed to disable plugin: {e}
- hook write-deny ensure failed: {e}
AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31).
Data as JSON: /api/errors/295f824e806e555f.
Report an issue: GitHub.