Skip to content

fix(repo-hygiene): validate clean-batch plan records against requested --tier (#1081)#1088

Draft
kyle-sexton wants to merge 1 commit into
mainfrom
fix/1081-clean-batch-tier-validation
Draft

fix(repo-hygiene): validate clean-batch plan records against requested --tier (#1081)#1088
kyle-sexton wants to merge 1 commit into
mainfrom
fix/1081-clean-batch-tier-validation

Conversation

@kyle-sexton

@kyle-sexton kyle-sexton commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Summary

clean-batch.sh --apply --batch-plan <path> dispatched purely on each plan line's
kind (REPO/GITDIR); the apply-time --tier flag was informational only (it set
the banner and whether the summary reports gitdirs=). A stale or swapped plan
therefore executed its full gated content while the banner named a narrower tier —
e.g. a --tier build dry-run plan applied with --tier caches removed both bin/
and .pytest_cache/ while printing Tier: caches. Bounded defect: every removed
path was still enumerated and confirmation-gated at plan creation, so nothing
un-gated is ever removed — the flaw is scope misrepresentation at apply time.

Fix

Per the triage decision on #1081 (validate, do not drop the flag), --apply now
pre-scans the whole plan before the banner and before touching any disk and
refuses it atomically (usage error, exit 2, nothing removed, no apply banner) when a
record the requested --tier does not authorize is present:

Record Authorized under
REPO token caches --tier caches
REPO token build --tier build, --tier all
GITDIR --tier git, --tier all

This rejects a build-class REPO record under --tier caches and gates the
GITDIR arm on a git-bearing tier, exactly as the triage resolved. Atomic refusal
(vs. per-record rejection) means the apply banner is never printed for a mismatched
plan, so the misrepresentation is structurally impossible rather than merely caught
after some records already ran. A malformed/unrecognized token is still handled as
before by the apply loop's per-record fail-closed guard (exit 1) — a distinct error
class (structural corruption) from a well-formed plan built for the wrong tier.

Authorization uses a dedicated tier_repo_token helper (empty for the git tier) —
deliberately not the existing manifest_child_token, which returns build for
git and would wrongly authorize a build REPO record under --tier git.

Header / usage / clean-batch.md exit-taxonomy and gated-set docs updated to match.

Verification

All commands run in the worktree on this branch.

Full test suite (57 assertions, incl. 3 new tier-authorization tests) + shellcheck:

$ shellcheck plugins/repo-hygiene/skills/clean/scripts/clean-batch.sh && echo CLEAN
CLEAN
$ bash plugins/repo-hygiene/skills/clean/scripts/clean-batch.test.sh | tail -8
PASS: [35] build plan under --tier caches is refused (exit 2)
PASS: [36] tier-mismatch refusal reported
PASS: [37] no apply banner printed on tier mismatch
PASS: [38] build dir NOT removed by mismatched apply
PASS: [39] cache NOT removed by mismatched apply
PASS: [40] git plan under --tier caches is refused (exit 2)
PASS: [41] GITDIR-under-non-git refusal reported
PASS: [42] all plan under --tier all applies (exit 0)
...
clean-batch.test.sh: all passed

Exact issue repro (a --tier build dry-run plan applied with --tier caches) —
both bin/ and .pytest_cache/ survive:

=== plan contents (REPO/build record folds caches) ===
REPO	<demo>	build	<tmp>/000-....manifest

=== 2. --tier caches --apply --batch-plan <build-plan>  (the bug scenario) ===
clean-batch.sh: plan record does not match --tier caches: a 'build' REPO record is not authorized (plan built for a different tier?). Re-run --dry-run --tier caches.
exit=2
=== 3. artifacts still present? ===
<demo>/.pytest_cache/x
<demo>/bin/b
BOTH SURVIVE -- scope misrepresentation prevented

Version: plugins/repo-hygiene bumped 0.7.0 → 0.7.1 (patch bugfix) with a matching
CHANGELOG [0.7.1] Fixed entry.

Related

Closes #1081

Work-class: C3 (bug-fix-shaped) — attended triage 2026-07-23, operator-ratified. 🤖

…d --tier (#1081)

clean-batch.sh --apply dispatched purely on each plan line's REPO/GITDIR kind;
the apply-time --tier flag was informational only, so a stale or swapped plan
executed its full gated content while the banner named a narrower tier (a
--tier build dry-run plan applied with --tier caches removed both bin/ and
.pytest_cache/ while printing Tier: caches).

Apply now pre-scans the plan and refuses it atomically (usage error, exit 2,
nothing removed, no apply banner) when it carries a record the requested tier
does not authorize: a build REPO record under --tier caches, a caches record
under build, or a GITDIR record under a non-git tier. The removal set is
unchanged; this closes the scope-misrepresentation between --tier and what
apply actually removes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@kyle-sexton kyle-sexton added automated Opened by automation. do-not-merge Hard merge gate: do not merge while applied. labels Jul 22, 2026
@claude

claude Bot commented Jul 22, 2026

Copy link
Copy Markdown

Claude encountered an error after 1s —— View job


I'll analyze this and get back to you.

@claude

claude Bot commented Jul 22, 2026

Copy link
Copy Markdown

Claude encountered an error after 0s —— View job


I'll analyze this and get back to you.

@github-actions

Copy link
Copy Markdown

Warning

Automated review did not complete — this is an infrastructure failure, not a review verdict.

Treat any Claude comment on this PR (including a placeholder like "I'll analyze this and get back to you") as incomplete, not "no findings."

Re-running the job, or pushing a new commit, will retry the review.

@github-actions

Copy link
Copy Markdown

Warning

Automated security review did not complete — this is an infrastructure failure, not a review verdict.

Treat any Claude comment on this PR (including a placeholder like "I'll analyze this and get back to you") as incomplete, not "no findings."

Re-running the job, or pushing a new commit, will retry the review.

@kyle-sexton

Copy link
Copy Markdown
Contributor Author

🔒 babysit-prs lane claiming this PR for on-branch fix work this cycle (amendment-round: 16, safe tier). Will fix clear branch-owned findings and push; will not resolve threads or merge (safe tier).

@kyle-sexton kyle-sexton added agent-ready Fully specified and briefed; eligible for autonomous pickup from the frontier. and removed do-not-merge Hard merge gate: do not merge while applied. labels Jul 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent-ready Fully specified and briefed; eligible for autonomous pickup from the frontier. automated Opened by automation.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

repo-hygiene: clean-batch apply should validate plan records against the requested --tier

1 participant