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
- Treat the error as authoritative: the original path no longer exists; re-open by a known path or drop the stale handle
- Restore/recreate the file at its original path if the caller still needs it there
- 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
- Keep a known-path copy instead of re-deriving paths from live descriptors
- Expect rename-over writes (editors, log rotation) to invalidate open-file paths on Linux
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
- failed to run `git init` in directory '{}'
- Could not detect a working directory in the container. Set E
- Could not detect a working directory; set EVAL_CLI_WORKDIR v
- unknown benchmark '{selector}' (valid: {valid})
- blocking sender returned without value
AI-assisted analysis of zed-industries/zed@9d272b0363 (2026-08-20).
Data as JSON: /api/errors/51f6f2d8e9f4e6b8.
Report an issue: GitHub.