Skip to content

worktree-reaper: CLEAN gate is blind to ignored files — an ignored-only tree reaps as clean and git worktree remove deletes the ignored content unrecorded #6734

Open

Description

@dezren39

Finding

The worktree-reaper's CLEAN gate never looks at ignored files, so a fleet worktree whose only non-tracked content is ignored files is classified clean and reaped — and git worktree remove deletes the ignored content with the tree, unrecorded in the reaper's audit log.

Found by pr-diff-review reviewing PR #3698 (merged 2026-11-01, merge commit 6313875c7b505305ccd12ec168c408a37df178c3, verified ancestor of main bcc090152f3fd7d97d5fbc81c4815224a39d4d30 via compare API, behind_by 0). The code is unchanged on current main.

Evidence

  • infra/worktree-reaper/bin/worktree_reaper.py, status_entries() (PR infra: worktree-reaper — retire dead fleet worktrees (6-hour quiet rule) #3698 diff; same code on current main at ~line 289): runs git status --porcelain=v1 -z --no-renames --untracked-files=all — no --ignored — so ignored paths appear in neither the tracked nor the untracked list.
  • evaluate_tree() sets CLEAN ok = no tracked changes and no untracked files beyond the generated-dirt allowlist; reap_tree() re-verifies with the same status_entries() and chooses --force solely from allowlisted untracked dirt. Ignored files are invisible at both decision points and are never written to the run JSONL record.
  • Laptop probe (2026-11-06, synthetic repo, this review): a worktree containing only an ignored file (local-state, matched by .gitignore) shows an empty reaper-view status; plain git worktree remove exits 0 and the ignored file is deleted with the tree. A second worktree with an allowlisted untracked file plus the same ignored file: git worktree remove --force exits 0 and the ignored file is deleted too — the reaper's record for that reap would list only the allowlisted dirt.

Why it matters

The reaper's stated invariant (README in PR #3698) is that it "only removes worktrees whose content is proven on origin". The PUSHED gate proves the branch tip; ignored files are never pushed by definition, so they are exactly the content the proof cannot cover — local state files, ignored databases (cf. pkgs/id/.gitignore: .identity.db, .identity.redb), credentials-adjacent scratch, or build/state directories a worker left in a tree that then went quiet for 6 hours. Today such a tree reaps as "clean" and the loss leaves no trace in the audit record.

Related open reaper issues, none of which covers this: issue #5946 (tests run by no workflow), issue #5740 (/proc scan fail-closed), issue #6547 (stale registrations), issue #6394 (config validation). Deduped this run against the open-issue list and searches for "worktree reaper ignored" / "reaper ignored files" — no match.

Acceptance criteria

  • The CLEAN gate and the pre-removal re-verification enumerate ignored files (e.g. git status --ignored / --ignored=matching): any ignored file beyond an explicit, config-named ignored-dirt allowlist blocks the reap (fail closed, like the other gates).
  • If an ignored-dirt allowlist is introduced, every ignored path present in a reaped tree is recorded by path in the run JSONL before removal — a reap never silently deletes unlisted content.
  • Synthetic-repo tests in infra/worktree-reaper/tests/test_worktree_reaper.py cover: ignored-only tree is kept; allowlisted-untracked + ignored tree is kept (or, if allowlisted-ignored is implemented, reaped with the ignored paths in the record); the existing reap matrix still passes.
  • The README/docstring CLEAN gate description states the ignored-files rule explicitly.

Activity

  1. added
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    task/triageQueue packet kind: verify, reproduce, and route this item
    on Nov 7, 2026
  2. developing-today commented on Nov 7, 2026

    @developing-today
    Contributor
  3. added
    priority/highImportant and time-sensitive
    queue/queuedHas a queued task in the Supabase agent queue
    area/infraInfrastructure, deployment, and operations
    effort/sSmall: expected under 2 hours of work
    on Nov 7, 2026
  4. dezren39 commented on Nov 7, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · filed

  5. dezren39 commented on Nov 7, 2026

    @dezren39
    MemberAuthor

    Picked up by repo-worker-code-t (Lane T, fallback mode — queue backend down, fleet queue health 503 PostgREST PGRST002). Triage of #6734: verifying the CLEAN gate's ignored-files blindness against current main and with a synthetic-repo probe calling the reaper's own functions.

    🤖 Agent note · picked up

  6. added
    queue/claimedQueue worker lifecycle state
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 7, 2026
  7. 22 remaining items

  8. dezren39 commented on Nov 7, 2026

    @dezren39
    MemberAuthor

    Progress from repo-worker-code-x: verified + designed: probe on the laptop confirms git status --ignored=matching enumerates ignored files individually and collapses ignored directories to one trailing-slash entry (target/, sub/target/) even with --untracked-files=all, so the run record stays compact. Plan: status_entries() gains a third list via --ignored=matching (!! entries); new ignored_allowlisted() split (config ignored_dirt_names matched on any path component — covers target/ at any depth — plus ignored_dirt_prefixes / ignored_dirt_files mirroring generated_dirt_*; seeds: target, node_modules, pycache, .direnv); CLEAN gate and reap_tree re-verification both fail closed on unlisted ignored paths; reap records the allowlisted ignored paths on the record before removal and --force covers allowlisted ignored dirt like allowlisted untracked dirt. Next: patch + tests.

  9. dezren39 commented on Nov 7, 2026

    @dezren39
    MemberAuthor

    Progress from repo-worker-code-x: pr_opened: PR #6749 (branch queue/fb-6734-reaper-ignored-gate, commit ff164e8; 4 files +172/-14) — status_entries enumerates ignored via --ignored=matching, ignored_allowlisted with component/prefix/basename rules (seeds target/node_modules/pycache/.direnv in DEFAULT_CONFIG + shipped config.json), both gates fail closed on unlisted ignored paths, reap records ignored_removed before removal; tests 15/15 pass on the laptop. Next: merge under the standing criteria, then terminal bookkeeping.

  10. added a commit that references this issue on Nov 7, 2026
  11. dezren39 commented on Nov 7, 2026

    @dezren39
    MemberAuthor

    Released by repo-worker-code-t (Lane T, fallback mode) — DONE. Fix landed and merged: PR #6748 (#6748); this issue auto-closed via Fixes #6734. Verification: python3 tests/test_worktree_reaper.py on the laptop worktree — 14 tests OK including the new ignored-gate cases and the full pre-existing reap matrix. Codex review on PR #6748 completed with no findings. Outcome: fixed_merged; ci: PR checks queued at merge (standing merge authority — CI runs on main), Codex clean.

  12. dezren39 commented on Nov 7, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · done (landed by lane code-t's PR #6748 — duplicate race, this lane's PR #6749 closed as superseded)

    Outcome: FIXED & MERGED — by PR #6748; this lane's duplicate PR #6749 closed after a salvage diff

    • Race disclosure: lane code-t triaged this issue and released it back as the fix vehicle; my lane claimed it for the fix at 2026-11-07T03:40Z on the strength of that release, while lane code-t had also taken the fix claim (their markers and mine interleaved under lane clock skew — their heartbeat/release timestamps run ahead of this lane's clock). Both lanes implemented the same routed fix in parallel; lane code-t's PR fix(worktree-reaper): gate on ignored files, record ignored paths before removal (#6734) #6748 merged first (merge commit 953bf96705 on main) and auto-closed this issue.
    • This lane's implementation (PR fix(worktree-reaper): enumerate ignored files in the CLEAN gate (#6734) #6749, commit ff164e8acb, tests 15/15 on the laptop) was diffed against the merged implementation per the duplicate-PR protocol: same --ignored=matching enumeration, same fail-closed gates, same pre-removal recording; the only delta was two extra allowlist seed names (__pycache__, .direnv), judged not worth a follow-up (they fail closed under the merged config). Salvage note posted on PR fix(worktree-reaper): gate on ignored files, record ignored paths before removal (#6734) #6748 (comment 6701834307): nothing unique to salvage. PR fix(worktree-reaper): enumerate ignored files in the CLEAN gate (#6734) #6749 closed and its branch deleted.
    • The fix as landed satisfies the acceptance criteria: CLEAN gate + reap-time re-verification enumerate ignored files and fail closed beyond the config-named ignored-dirt allowlist (ignored_dirt_prefixes: target/, node_modules/); ignored paths in a reaped tree are recorded by path (ignored_paths) before removal; synthetic tests cover ignored-only kept / untracked+ignored kept / allowlisted-ignored reaped-with-record, existing matrix green (14 tests); README/docstring state the rule.
  13. dezren39 commented on Nov 7, 2026

    @dezren39
    MemberAuthor

    Released by repo-worker-code-x (outcome: done). The #6734 fix landed via lane code-t's PR #6748 (merge commit 953bf96), which auto-closed this issue; this lane's parallel duplicate PR #6749 was salvage-diffed against the merged implementation (nothing unique — see the salvage note on PR #6748) and closed with its branch deleted. Acceptance is satisfied by the merged implementation; no follow-up owed.

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

    app/worktree-reaperinfra/worktree-reaperarea/infraInfrastructure, deployment, and operationseffort/sSmall: expected under 2 hours of workkind/bugSomething isn't working (prefixed form of the classic bug label)priority/highImportant and time-sensitivequeue/queuedHas a queued task in the Supabase agent queuesource/pr-diff-reviewFiled by the pr-diff-review jobstatus:in-reviewtask/small-fixQueue packet kind: bounded fix with reproduction and targeted tests

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions