Skip to content

2.0.0-rc.7: worktree cleanup keeps ignored files, scratch, and reflog-only commits - #26

Merged
chhoumann merged 9 commits into
mainfrom
v2/rc7
Oct 16, 2026
Merged

chhoumann merged 9 commits into
mainfrom
v2/rc7

Conversation

@chhoumann

@chhoumann chhoumann commented Oct 16, 2026 •

Copy link
Copy Markdown
Owner

Why

Worktree cleanup could delete work that existed nowhere else (cursor/plugins#449). The audit bucketed a worktree safe when it held an ignored .env, untracked notes, or a commit only its HEAD reflog reached. Step 5 then removed it with git worktree remove --force and rm -rf. The audit also called itself read-only, yet it fetched (cursor/plugins#470), and its git status rewrote each worktree's index.

What changed

  • worktree-audit.sh holds a worktree with ignored files outside build and cache directories, untracked scratch, a commit only HEAD or its reflog reaches, or a status or reflog git cannot read. The ignored and reflog checks port Michael Denyer's pstack-claude #277 to Lauren's bash script. The audit fetches only under --fetch.
  • Cleanup step 5 runs a plain git worktree remove and stops when it refuses, as sergchil's jutsu da94424 does. bun tools/generate.mjs --check rejects a playbook that force-removes a worktree, runs a recursive rm or git clean -x, or prunes worktrees in bulk.
  • The plugin icon drops a signed C2PA caBX chunk (pstack-claude #271). The pixels do not change.

Scope

  • Both changed files of Lauren's are declared forks that point at #449.
  • It leaves out #470's closed-PR and unpushed-branch items. Branch refs survive removal, so neither loses work with the worktree.
  • It leaves out tracked edits that assume-unchanged or skip-worktree hide. They read clean here, as they do upstream.

Blast Radius

Only the worktree cleanup playbook and its audit change behavior. Cleanup now pauses on more worktrees and asks before it removes them. Apart from edits those two index flags hide, a removal deletes without a decision only ignored build and cache directories such as node_modules. Neither hooks file changes.

Verification

  • Throwaway repositories: on main, the audit bucketed an ignored-.env worktree and a detached-commit worktree safe, and step 5 lost both. On this branch both hold, step 5 removes only the clean merged worktrees, and the .env and the commit survive git gc.
  • 12 new tests fail on main and pass here. GIT_CONFIG_GLOBAL=/dev/null bun test tests/ passes 550 with 0 failures, and the scripts suite, generate --check, and the sync dry runs at 99743e9 pass.
  • Claude Fable 5.1 and GPT-6 Astra reviewed the change in three rounds, and a separate run of the real audit passed 12 cases. Round 1 found a status.showUntrackedFiles=no regression. Every finding that could lose work is fixed, apart from the index-flag gap above, and the ones left open only add holds. Cursor Bugbot then found two gaps in the check's git rm exception, both fixed with tests.

The audit calls a worktree safe when removing it deletes an ignored file
that exists nowhere else or orphans a commit only its HEAD reflog reaches,
and it fetches and refreshes indexes while it claims to be read-only. These
tests run the real script on real repositories and fail on main. They port
the ignored-file and reflog cases from Michael Denyer's pstack-claude #277,
and add the generator check from sergchil/jutsu da94424 that rejects a
playbook which force-removes a worktree.
…mmits

worktree-audit.sh bucketed a worktree safe when removing it would delete an
ignored .env or orphan a commit only its HEAD reflog reaches. It now reads
ignored paths with --ignored=matching and counts those outside a named list
of build and cache directories as ignored:N (hold-ignored). It walks the
worktree's HEAD reflog and counts commits no branch, tag, remote, or branch
reflog reaches as reflog:N (hold-reflog). Both ports Michael Denyer's
pstack-claude #277 to Lauren's bash script.

The audit no longer fetches unless --fetch is given, and it sets
GIT_OPTIONAL_LOCKS=0, so its git status no longer rewrites the index of a
worktree an agent may be using. The read-only claim is now true by default.
Cleanup step 5 ran git worktree remove --force and then rm -rf on whatever
survived, which deletes untracked and ignored files that exist nowhere else
(cursor/plugins#449). It now runs a plain remove and treats its refusal as
the stop. Step 4 names the ignored and reflog counts the audit adds, pauses
on untracked scratch, and deletes only paths the user approves with a
targeted git clean.

The generator check fails when any playbook force-removes a worktree or runs
rm -rf, as sergchil/jutsu da94424 does, so a sync cannot bring the step
back silently.
Both icon copies carried a caBX chunk with an image generator's signed C2PA
manifest, whose certificate and signature text a plugin directory screen can
read as a credential. The files are Michael Denyer's from pstack-claude #271:
every other chunk is byte-identical, so the pixels are unchanged. His test
fails on any tracked PNG with a caBX chunk.
The audit still bucketed a worktree with only untracked files safe, as
Lauren's script treats them as throwaway. cursor/plugins#449 covers them
too, and the cleanup playbook now pauses on scratch and a plain remove
refuses it, so the bucket says hold-scratch and the advice matches the
playbook.
Two reviewers found that a status.showUntrackedFiles=no config made the new
git status call fail, which the audit read as clean, so a worktree with an
ignored .env bucketed safe again. Under that config a plain git worktree
remove also deletes untracked files without refusing. The status call now
passes --untracked-files=normal. A status call or HEAD reflog walk that
fails holds the worktree as unknown or reflog:? instead of reading clean.

A detached commit HEAD still points at is held after gc expires its reflog,
since HEAD itself now feeds the reflog walk. An ignored file under a tracked
directory that happens to be named build or target is held, since only the
last component of an ignored path can mark it disposable. wip and scratch
print together.

Step 4 lists ignored files with the audit's own status call, names unknown,
reads a prunable worktree's HEAD and reflog from the main checkout, and
deletes approved scratch with --literal-pathspecs, so notes[1].md no longer
also matches notes1.md. Step 5 says what a plain remove deletes. The
generator check also rejects -ff, a recursive rm with its flags in any
order, git clean -x, and git worktree prune, and lets git rm -r through.
VERSION, the manifests, and package.json move to 2.0.0-rc.7. CHANGES.md
gets the entry with its behavior-delta table. NOTICE.md and NOTICE-skills.md
credit Michael Denyer's pstack-claude #277 and #271 and sergchil/jutsu
da94424, CONTRIBUTING.md names the two new forks, and the README's pinned
installer command names the new tag.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread tools/generate.mjs Outdated
The exception that let `git -C <path> rm -r --cached` through excused a
recursive rm after any earlier git on the same line, so a line such as
"If git refuses, rm -rf the directory." passed the check. It now excuses
only `git rm` and `git -C <path> rm`. Cursor Bugbot found it on #26.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 65680c3. Configure here.

Comment thread tools/generate.mjs Outdated
The path in the `git -C <path> rm` exception matched any non-space run, so
a closing backtick or a glued semicolon became part of the path and a
recursive rm after it passed the check. The path now stops at a backtick,
`;`, `&`, or `|`. Cursor Bugbot found it on #26.
@chhoumann
chhoumann merged commit c1b22a1 into main Oct 16, 2026
11 checks passed
@chhoumann
chhoumann deleted the v2/rc7 branch October 16, 2026 17:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant