Summary
pu clean/pu clean --all can leave the worktree directory (full checkout + node_modules) orphaned on disk — invisible to pu status and git worktree list — while still deregistering the branch and manifest entry as if cleanup succeeded. Found this after 27 stale worktree dirs silently accumulated ~61GB in .pu/worktrees/ on a single project.
Root cause
crates/pu-engine/src/engine/worktree_ops.rs:153, in handle_delete_worktree:
// 2. Remove git worktree directory
let root_path = Path::new(project_root);
let wt_path = paths::worktree_path(root_path, worktree_id);
git::remove_worktree(root_path, &wt_path).await.ok();
git::remove_worktree (crates/pu-engine/src/git.rs:81-85) runs:
run_git(&["worktree", "remove", "--force", &wt_str], repo_root).await?;
If this git worktree remove --force fails partway through — e.g. a lingering process (bun/node) still holding a file handle somewhere under node_modules, or a permission error during the recursive delete — git can still deregister the worktree from its own admin metadata (.git/worktrees/<id>) while the physical directory removal doesn't fully complete. run_git correctly returns an Err in that case (non-zero exit status + stderr), but handle_delete_worktree discards it with .ok() and proceeds anyway:
- Deletes the local branch
- Deletes the remote branch
- Removes the worktree entry from the manifest
From that point on, both git worktree list and pu status report the worktree as fully gone, but the directory is still on disk, fully orphaned, with no tooling able to find or reclaim it. There's no post-removal check (e.g. wt_path.exists()) and no logging of the failure anywhere the user can see it.
Impact
Silent, unbounded disk leak over time as agents are spawned/cleaned repeatedly. On one project this accumulated to ~61GB across 27 orphaned directories before being noticed via manual du.
Suggested fix
In handle_delete_worktree:
- Check the
Result from remove_worktree instead of .ok()-discarding it.
- If it errors, verify whether
wt_path still exists on disk:
- If gone, proceed as today (transient git warning, no real problem).
- If still present, either retry with a direct
std::fs::remove_dir_all fallback, or keep the manifest entry (marked with an error/stale status) instead of removing it, so pu status/pu clean can still see and retry it later rather than losing all reference to it.
- Log the failure so it's visible in
daemon.log / to the user instead of disappearing entirely.
Happy to send a PR for this if useful.
Summary
pu clean/pu clean --allcan leave the worktree directory (full checkout +node_modules) orphaned on disk — invisible topu statusandgit worktree list— while still deregistering the branch and manifest entry as if cleanup succeeded. Found this after 27 stale worktree dirs silently accumulated ~61GB in.pu/worktrees/on a single project.Root cause
crates/pu-engine/src/engine/worktree_ops.rs:153, inhandle_delete_worktree:git::remove_worktree(crates/pu-engine/src/git.rs:81-85) runs:If this
git worktree remove --forcefails partway through — e.g. a lingering process (bun/node) still holding a file handle somewhere undernode_modules, or a permission error during the recursive delete — git can still deregister the worktree from its own admin metadata (.git/worktrees/<id>) while the physical directory removal doesn't fully complete.run_gitcorrectly returns anErrin that case (non-zero exit status + stderr), buthandle_delete_worktreediscards it with.ok()and proceeds anyway:From that point on, both
git worktree listandpu statusreport the worktree as fully gone, but the directory is still on disk, fully orphaned, with no tooling able to find or reclaim it. There's no post-removal check (e.g.wt_path.exists()) and no logging of the failure anywhere the user can see it.Impact
Silent, unbounded disk leak over time as agents are spawned/cleaned repeatedly. On one project this accumulated to ~61GB across 27 orphaned directories before being noticed via manual
du.Suggested fix
In
handle_delete_worktree:Resultfromremove_worktreeinstead of.ok()-discarding it.wt_pathstill exists on disk:std::fs::remove_dir_allfallback, or keep the manifest entry (marked with an error/stale status) instead of removing it, sopu status/pu cleancan still see and retry it later rather than losing all reference to it.daemon.log/ to the user instead of disappearing entirely.Happy to send a PR for this if useful.