You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
poteto-mode worktree cleanup: untracked files are labelled "scratch, safe to drop", and paths with spaces are truncated #1054
Found against pstack 0.15.34 at cursor/plugins 519d36aa98955bf680db01bcde5fdfba686dc901 (harness/pstack/claude-code/skills/poteto-mode/). Line numbers refer to that commit.
scripts/worktree-audit.sh, together with playbooks/worktree-cleanup.md, can lead an agent to delete work that
exists nowhere in git. There are two defects.
A. Untracked files are classed as disposable.
worktree-audit.sh:93-97: a worktree whose git status --porcelain has only ?? lines becomes scratch:N. The
bucket logic (:125) then rates it safe when the branch is merged or has a PR.
worktree-cleanup.md:6 says "scratch:N is untracked throwaway, safe to drop", and step 7 runs git worktree remove --force <path>.
Being untracked doesn't make a file disposable. A new source file that hasn't been git added yet is the most common
form of unfinished work.
Repro:
Create a worktree on a branch that is already merged (or has a merged PR).
Add an untracked new-provider-parser.ts with unfinished code.
Run the audit. The row is scratch:1, bucket safe.
Follow the playbook. The worktree is force-removed and the file is lost, without the decision the playbook requires
for tracked WIP.
B. Path parsing and failed inspection.
worktree-audit.sh:31 and :80 use awk '{print $2}' on git worktree list --porcelain, which drops everything
after the first space. A worktree at /tmp/my worktrees/branch-one is read as /tmp/my.
:93: git -C "$wt" status --porcelain 2>/dev/null. When $wt is wrong or git fails, the output is empty and the
row is reported as clean, so a failed inspection looks safe.
:115-116: transcript matching passes the path to rg -e as a regex (. and + match other paths), and xargs stat splits filenames on whitespace.
Treat untracked files that aren't ignored as WIP (hold-wip), the same as tracked edits. Only drop them after the
user has confirmed them by name, or after they have been preserved (stash with -u, or copied out). Change the
playbook text to match.
Parse git worktree list --porcelain -z (NUL-delimited) and keep the full path.
When rev-parse, log or status fails, make the row unknown / held, never clean.
Use rg -F (fixed strings) for the transcript match, and rg -l0 … | xargs -0 stat.
Add tests for a worktree with only untracked files, a path with a space, and a git failure.
Hey @MaxHarris-1, part A has an open fix already: @dajiaohuang's #459 runs git status --untracked-files=all --ignored=matching, counts untracked and ignored paths as wip:N, removes the scratch: class and rewrites the playbook line, and #770 (mine) stacks #459 on #492 with a bun test harness that builds real worktrees and a fake gh, including the "only untracked files on a merged branch" case you describe. Both are open and unreviewed; #770 is being rebased now that #1043 touched package.json.
Part B is mostly covered too, and I missed it in the first version of this comment: @rudycelekli's #660 replaces the awk '{print $2}' on git worktree list --porcelain with sed -n 's/^worktree //p' so a path with spaces stays whole, and switches the transcript lookup to rg -l -0 -F with xargs -0 stat, which is your NUL-delimited and literal-match suggestion. It carries a worktree-audit.test.mjs that audits a spaced worktree path (2 pass under node --test), and git merge-tree is clean against main and against #770. The one piece of part B no open PR touches is the empty-output fallback: porcelain=$(git -C "$wt" status --porcelain 2>/dev/null) followed by [ -z "$porcelain" ] reads a failed git status as clean, and #459/#770 keep that shape. That is the remaining concrete gap, and it sits on the same lines as #770, so the cleanest landing is on top of that stack.
Correction: the first version of this comment said part B had no open PR; #660 was already open and unreviewed.
Activity
deanrie commentedon Oct 28, 2026
Hey @MaxHarris-1, part A has an open fix already: @dajiaohuang's #459 runs
git status --untracked-files=all --ignored=matching, counts untracked and ignored paths aswip:N, removes thescratch:class and rewrites the playbook line, and #770 (mine) stacks #459 on #492 with a bun test harness that builds real worktrees and a fakegh, including the "only untracked files on a merged branch" case you describe. Both are open and unreviewed; #770 is being rebased now that #1043 touchedpackage.json.Part B is mostly covered too, and I missed it in the first version of this comment: @rudycelekli's #660 replaces the
awk '{print $2}'ongit worktree list --porcelainwithsed -n 's/^worktree //p'so a path with spaces stays whole, and switches the transcript lookup torg -l -0 -Fwithxargs -0 stat, which is your NUL-delimited and literal-match suggestion. It carries aworktree-audit.test.mjsthat audits a spaced worktree path (2 pass undernode --test), andgit merge-treeis clean againstmainand against #770. The one piece of part B no open PR touches is the empty-output fallback:porcelain=$(git -C "$wt" status --porcelain 2>/dev/null)followed by[ -z "$porcelain" ]reads a failedgit statusasclean, and #459/#770 keep that shape. That is the remaining concrete gap, and it sits on the same lines as #770, so the cleanest landing is on top of that stack.Correction: the first version of this comment said part B had no open PR; #660 was already open and unreviewed.