Skip to content

Rename worktrees to trash before deleting#59240

Draft
MartinYe1234 wants to merge 3 commits into
mainfrom
fix-worktree-archive-enotempty
Draft

Rename worktrees to trash before deleting#59240
MartinYe1234 wants to merge 3 commits into
mainfrom
fix-worktree-archive-enotempty

Conversation

@MartinYe1234

Copy link
Copy Markdown
Contributor

Removing a worktree deleted its directory in place with a recursive
unlink + rmdir, then ran git worktree remove. When another process
was still writing into the worktree (a cargo check from rust-analyzer,
a user terminal running a build, another editor, ...), a file created
between the unlink and the final rmdir made it fail with ENOTEMPTY,
aborting the archive and leaving a partially deleted tree on disk.

This atomically renames the worktree into a .trash sibling of the
managed worktrees directory and deletes it in a detached background task
with retry/backoff. The rename always succeeds even with live writers,
so the archive completes immediately and stray writes land harmlessly in
the trash entry until the writers wind down. A .trash/.gitignore keeps
the entry out of git, leftover entries are swept (age-gated) on
repository registration, and if git worktree remove fails after the
rename the directory is renamed back so the worktree is left intact.

Closes AI-400

Release Notes:

  • Fixed git worktree deletion sometimes failing with a "Directory not empty" error (and the associated thread failing to archive) when a build or language server was still running in the worktree.

Removing a worktree previously deleted its directory in place with a
recursive unlink+rmdir, then ran `git worktree remove`. When another
process (e.g. a `cargo check` spawned by rust-analyzer, or a user
terminal) was still writing into the worktree, the final rmdir raced
with those writes and failed with ENOTEMPTY, aborting the archive and
leaving a partially deleted tree.

Instead, atomically rename the worktree into a `.trash` sibling of the
managed worktrees directory, then delete it in a detached background
task with retry/backoff. The rename is a single atomic operation that
succeeds even with live writers, so the archive completes immediately
and stray writes land harmlessly in the trash entry until the writers
wind down. A `.trash/.gitignore` keeps the entry out of git when the
worktrees directory is configured inside a working tree, and leftover
entries are swept (age-gated) when a repository is registered.

If `git worktree remove` fails after the rename, the directory is
renamed back so an error from remove_worktree still means the worktree
is intact at its original path.
@MartinYe1234 MartinYe1234 self-assigned this Jun 13, 2026
@cla-bot cla-bot Bot added the cla-signed The user has signed the Contributor License Agreement label Jun 13, 2026
@zed-community-bot zed-community-bot Bot added the staff Pull requests authored by a current member of Zed staff label Jun 13, 2026
MartinYe1234 and others added 2 commits June 12, 2026 22:20
The sweep scheduled on repository registration issued background
filesystem operations that, on FakeFs, consumed the deterministic test
scheduler's RNG and perturbed the completion ordering of unrelated async
work, breaking editor hover-link tests. Skip it on the fake filesystem
(matching how fs watchers are handled) and cover the sweep logic by
calling it directly in tests.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla-signed The user has signed the Contributor License Agreement staff Pull requests authored by a current member of Zed staff

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant