chore: worktree hygiene rule and safe prune script - #722
Conversation
Sixty-plus stale worktrees of merged branches accumulated on the dev machine, each carrying a multi-gigabyte virtualenv, and together they exhausted the disk. Add a merge-ritual rule (the worktree's creator removes it after the PR merges) and scripts/prune_merged_worktrees.sh, which sweeps only worktrees that are clean, already ancestors of origin/dev, and untouched for 48 hours, so in-flight agent checkouts are never removed. Branches and commits are never deleted. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01262jKrQwepcSQtv1vk2chH
There was a problem hiding this comment.
Pull request overview
This PR introduces a documented “worktree hygiene” rule and adds an automated pruning script to safely remove long-merged git worktrees, aiming to prevent disk exhaustion from accumulating stale worktrees.
Changes:
- Add a Worktree Hygiene policy to
CLAUDE.mddescribing the expected cleanup ritual after PR merges. - Add
scripts/prune_merged_worktrees.sh, a guarded sweeper that removes worktrees only if they are clean, merged intoorigin/dev, and older than a minimum age (default 48h). - Support a
--dry-runmode for safe previewing of removals.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| scripts/prune_merged_worktrees.sh | New pruning script to remove merged, clean, aged worktrees and then run git worktree prune. |
| CLAUDE.md | Documents the worktree hygiene rule and points to the prune script for safe cleanup. |
Comments suppressed due to low confidence (1)
scripts/prune_merged_worktrees.sh:53
- The “recently touched” guard is based on the mtime of
$wt_path/.git. For linked worktrees,.gitis typically a static pointer file created when the worktree is added, so its mtime won’t reflect recent activity in the worktree. This can cause an actively used (but clean) worktree to be removed despite the MIN_AGE_HOURS intent.
wt_mtime=$(stat -f %m "$wt_path/.git" 2>/dev/null || stat -c %Y "$wt_path/.git" 2>/dev/null || echo 0)
age_hours=$(( (NOW_EPOCH - wt_mtime) / 3600 ))
if [ "$age_hours" -lt "$MIN_AGE_HOURS" ]; then
echo "keep (recent, ${age_hours}h): $wt_path"
continue
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c55e169ffa
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Porcelain paths are now stripped with sed rather than awk field splitting, so paths containing spaces survive. The staleness guard now takes the newest mtime of the linkage file and the admin git-dir's HEAD and index, because the linkage file is written once at creation and never again, which made every worktree look idle regardless of use. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01262jKrQwepcSQtv1vk2chH
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
scripts/prune_merged_worktrees.sh:31
git fetch -q origin devis unconditional underset -e, so the whole sweep exits if the repo has nooriginremote or the machine is offline. Since the script can safely operate using the existingorigin/devref when present, consider making the fetch best-effort and warning instead of aborting.
git -C "$REPO" fetch -q origin dev
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 02b6b18e67
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
GNU stat -f is filesystem mode where %m is the mount point, so the BSD-first probe order succeeded with a non-mtime string on Linux and made recently-touched worktrees look ancient; GNU -c %Y now probes first. The loop also skips the worktree the script is invoked from, since $REPO resolves to the linked worktree when run inside one. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01262jKrQwepcSQtv1vk2chH
Why
The dev machine accumulated 64 worktrees of long-merged branches (199 GB, mostly virtualenvs and node_modules), which ran the disk down to 11 GB free. The worktrees themselves carried no unmerged work: with merge-commit merges, an ancestor check against origin/dev classifies merged content reliably, and a manual sweep of 57 such worktrees recovered ~150 GB with zero loss.
What
Validation
Dry-run on the live dev machine correctly kept the unmerged spike and fix worktrees and both recently-touched agent worktrees, and listed nothing else (the backlog had just been swept manually using the same ancestor-check logic).
🤖 Generated with Claude Code
https://claude.ai/code/session_01262jKrQwepcSQtv1vk2chH