Skip to content

poteto-mode worktree cleanup: untracked files are labelled "scratch, safe to drop", and paths with spaces are truncated #1054

Open

Description

@MaxHarris-1

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:

  1. Create a worktree on a branch that is already merged (or has a merged PR).
  2. Add an untracked new-provider-parser.ts with unfinished code.
  3. Run the audit. The row is scratch:1, bucket safe.
  4. 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.

Repro (parsing):

Input:  worktree /tmp/my worktrees/branch-one
Output: /tmp/my

Suggested fix:

  • 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.

Activity

  1. deanrie commented on Oct 28, 2026

    @deanrie

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions