zed-industries/zed · error · anyhow::Error

file was deleted

Error message

file was deleted

What it means

On Linux, Fs::current_path for an open file resolves /proc/self/fd/<fd> via readlink. When the kernel has unlinked the file but the descriptor is still open, the symlink target carries the literal suffix " (deleted)"; the implementation detects that suffix and bails with "file was deleted" instead of returning a bogus path. This is Linux's standard way of exposing an open-but-unlinked file.

Source

Thrown at crates/fs/src/fs.rs:496

        anyhow::ensure!(result != -1, "fcntl returned -1");

        // SAFETY: `fcntl` will initialize the path buffer.
        let c_str = unsafe { CStr::from_ptr(path_buf.as_ptr().cast()) };
        anyhow::ensure!(!c_str.is_empty(), "Could find a path for the file handle");
        let path = PathBuf::from(OsStr::from_bytes(c_str.to_bytes()));
        Ok(path)
    }

    #[cfg(target_os = "linux")]
    fn current_path(&self, _: &Arc<dyn Fs>) -> Result<PathBuf> {
        let fd = self.as_fd();
        let fd_path = format!("/proc/self/fd/{}", fd.as_raw_fd());
        let new_path = std::fs::read_link(fd_path)?;
        if new_path
            .file_name()
            .is_some_and(|f| f.to_string_lossy().ends_with(" (deleted)"))
        {
            anyhow::bail!("file was deleted")
        };

        Ok(new_path)
    }

    #[cfg(target_os = "freebsd")]
    fn current_path(&self, _: &Arc<dyn Fs>) -> Result<PathBuf> {
        use std::{
            ffi::{CStr, OsStr},
            os::unix::ffi::OsStrExt,
        };

        let fd = self.as_fd();
        let mut kif = MaybeUninit::<libc::kinfo_file>::uninit();
        kif.kf_structsize = libc::KINFO_FILE_SIZE;

        let result = unsafe { libc::fcntl(fd.as_raw_fd(), libc::F_KINFO, kif.as_mut_ptr()) };
        anyhow::ensure!(result != -1, "fcntl returned -1");

View on GitHub (pinned to 9d272b0363)

Solutions

  1. Treat the error as authoritative: the original path no longer exists; re-open by a known path or drop the stale handle
  2. Restore/recreate the file at its original path if the caller still needs it there
  3. Avoid rename-over patterns when other processes hold the path open, or coordinate the replacement

Example fix

// before
let path = file.current_path(&fs).await?;

// after
let path = match file.current_path(&fs).await {
    Ok(path) => path,
    Err(e) if e.to_string().contains("file was deleted") => {
        // descriptor is open but unlinked; re-resolve from a known path
        known_path.clone()
    }
    Err(e) => return Err(e.into()),
};
Defensive patterns

Strategy: try-catch

Validate before calling

// liveness check before relying on the descriptor's path
let meta = fs.metadata(&known_path).await;
if meta.is_err() {
    // path already gone; don't ask current_path for it
}

Try / catch

let path = match file.current_path(&fs).await {
    Ok(path) => path,
    Err(e) if e.to_string().contains("file was deleted") => { /* re-open from known path */ }
    Err(e) => return Err(e.into()),
};

Prevention

When it happens

Trigger: Calling current_path on a File whose backing path was deleted or replaced while open — editors writing via rename-over, build systems removing outputs, log rotation, or a git operation rewriting the worktree.

Common situations: A worktree file is deleted/replaced by git checkout/clean while the editor holds it open; files under /tmp cleaned by a tmpwatcher; two processes racing where one renames over the other's open file.

Related errors


AI-assisted analysis of zed-industries/zed@9d272b0363 (2026-08-20). Data as JSON: /api/errors/51f6f2d8e9f4e6b8. Report an issue: GitHub.