Skip to content

feat(disk-hygiene): handoff-apply, a Linux route for an acknowledged checkout - #5541

Merged
kyle-sexton merged 22 commits into
mainfrom
feat/5178-linux-accept-unpublished-route
Sep 30, 2026
Merged

kyle-sexton merged 22 commits into
mainfrom
feat/5178-linux-accept-unpublished-route

Conversation

@kyle-sexton

@kyle-sexton kyle-sexton commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Closes #5178

Summary

On Linux an operator-acknowledged throwaway checkout had no deletion route inside the engine: handoff-verify ran only on Windows and macOS, and apply keeps VCS protection categorical. This adds handoff-apply, a Linux-only engine route for one exact approved path. The acknowledgement (accept_unpublished in vcs-evidence.json) is evaluated only in the handoff verification path; preview and token apply stay categorical, as the owner decided on the issue.

Fix

  • hygiene.py: new handoff-apply subcommand (--execute --snapshot --path --vcs-evidence --report --data-root). It refuses off Linux, runs handoff_verify in-process for the one path, deletes only on a clear verdict, repeats the non-VCS checks per entry, and waives only vcs-tracked-content. The verdict reports accept_unpublished.
  • The snapshot records .git without its descendants, so handoff-apply is the one lane that removes entries outside the snapshot: it empties the repository metadata fd-relative, refusing on a mount point, a consumer protection glob match (re-checked per child as the purge reaches it), an unreadable directory or a device change, and unlinking links rather than following them.
  • destructive_guard.py: the exact handoff-apply shape asks when the plugin is enabled (exact-engine-handoff-apply); the kill switch denies it (kill-switch-disabled-handoff-apply).
  • SKILL.md, safety-model.md, unsupported-platform-handoff.md, the fan-out worker brief and the README name the Linux route and keep the loss warning (unpushed commits and untracked or ignored files are lost).
  • disk-hygiene 0.35.0 with a CHANGELOG entry.

Verification

  • bash plugins/disk-hygiene/skills/clean/scripts/hygiene.test.sh (from skills/clean/scripts): 624 tests OK on the merged tree, covering the Linux route, an unacknowledged repository staying contested, the per-child protection recheck and the guard shapes.
  • check-changelog-parity.sh with --check, --check-order, --check-bump origin/main and --check-preserved origin/main, and validate-plugins.sh: pass.
  • Ruff check through the pinned wrapper and markdownlint: clean.

Related

🤖 Generated with Claude Code

kyle-sexton and others added 7 commits September 29, 2026 22:34
…te for an acknowledged checkout

On Linux, an operator-acknowledged throwaway checkout had no in-engine
deletion route: accept_unpublished exists only in handoff-verify, and preview
and apply keep VCS protection categorical.

handoff-apply takes --execute --snapshot --path --vcs-evidence --report
--data-root. It refuses off Linux through execution_blockers(), then runs
validate_handoff_paths, validate_vcs_evidence and handoff_verify in-process
for exactly that one path, and deletes only on a clear verdict. No verdict is
read from a file. preview() and apply_plan are unchanged, and the
acknowledgement is evaluated only inside handoff_verify; the report copies the
verdict's accept_unpublished entries so the ack shows in the result.

After the verdict, every non-VCS check apply_plan runs is repeated per entry:
target fd identity, fresh mounts, hard_protection with baseline and snapshot
names, consumer globs, same_removal_identity, is_linkish, handle state, and
the O_NOFOLLOW fd-relative removal. Only vcs-tracked-content is satisfied by
the verdict, and only when it verified the repository evidence.

Two additions beyond apply_plan's loop were required, because its anchored
removal cannot delete a git checkout: the snapshot records .git without
descendants, so the removal refuses it as not empty, and tracked_blocker
cannot answer for a path inside .git. The route relaxes the .git marker
protections through evidence_adjusted_protections, as handoff-verify does,
and empties each verified repository's .git fd-relative (links unlinked, never
followed; a mount point or consumer glob match inside it blocks the purge)
before the existing anchored rmdir.

The route has no plan, so no owner: the native-managed-report-only check has
nothing to read here.

handoff-apply is a new grammar subcommand rather than a second form of apply,
so the apply token shape and its guard admission are unchanged. The guard
denies the new name until it is wired.

Refs #5178

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…y removes anything

handoff-apply deletes a directory's entries deepest first, so the working tree
went before .git. The mount, consumer-glob and unreadable-directory checks on
the uninventoried contents of .git ran only when .git itself was reached, which
left a gutted checkout with .git intact.

Run those checks for every verified repository's .git before the first removal,
and keep the per-entry recheck as the race defense. A directory the walk cannot
read now blocks as needs-elevation or filesystem-state-unverified instead of
being skipped. The relaxed .git marker protections apply only when the verdict
verified the repository evidence.

Refs #5178

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
handoff-apply was declared in the grammar, so the classifier recognized it,
but _decide had no branch for it and denied it as not an exact engine command.

The guard now sorts every admitted subcommand into a hand-placed read-only or
mutating set. A mutating subcommand gets the hook-issued ask when execution is
enabled and a kill-switch denial, under its own rule, when it is not. The
exact handoff-apply shape asks with a prompt naming the one approved path and
the acknowledged loss of unpushed commits and untracked or ignored files. The
kill-switch denial text lists the read-only subcommands in grammar order
instead of a hand-written list.

A newly declared subcommand stays denied until it is placed in one set; a test
fails until every grammar subcommand is in exactly one.

Tests cover the ask, each malformed variant (missing --vcs-evidence, --plan
or --approval-token beside --path, repeated --path, a flag-shaped value,
missing or unauthorized --data-root, missing --execute), the kill switch, the
recorded rules, and unchanged apply, preview and handoff-verify verdicts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e 0.29.0

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Release handoff-apply as 0.30.0 above main's 0.29.0 and use
consumer_protection_matches where main removed consumer_path_protected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… does

handoff-apply is not a read-only evidence mode and not acknowledgement-only:
it deletes on any clear handoff-verify verdict, asks in the guard alongside
apply, and is the one lane that empties Git metadata contents the snapshot
never inventoried.

- README: the VCS exception, the guard's two ask shapes, the snapshot-only
  traversal claim and the standalone-checkout pointer now name handoff-apply.
- safety-model: state the purge and the checks that bound it (mount, consumer
  glob over a live walk, unreadable directory, per-directory device, links
  unlinked not followed), that the hard-protection name check is not applied
  to those contents, and drop the stale shape count and apply-only prompt claim.
- SKILL.md: section 6 says any clear verdict deletes; restore the
  unsupported-platform and Gotchas wording the route had compressed.
- CHANGELOG: describe the purge and the any-clear-verdict semantics.
- opaque_contents_blocker docstring lists what it checks; add a test that a
  fully verified checkout is deleted without an acknowledgement.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@kyle-sexton
kyle-sexton marked this pull request as ready for review September 30, 2026 03:38
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-30T03:42:17.583826Z 0adbdde Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0adbddee53

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread plugins/disk-hygiene/skills/clean/scripts/hygiene.py
@claude

claude Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 4m 42s —— View job


Security review complete

  • Gather context (PR diff, files changed)
  • Read changed files under the security lens
  • Check instruction-surface deletions (CLAUDE.md/AGENTS.md/rules/skills)
  • Hunt for logic flaws, injection, trust-boundary issues in hygiene.py / destructive_guard.py / engine_grammar.py
  • Adversarially verify candidate findings
  • Post final findings

No security issues found. This PR adds handoff-apply, a new Linux-only mutation path that verifies and deletes a single operator-approved standalone Git checkout in one guarded step, plus the .git-metadata purge it needs (the snapshot never inventories .git's descendants). I reviewed the new/changed logic in:

Things specifically checked and found sound:

  • The evidence/path plumbing (validate_handoff_paths, validate_vcs_evidence) keeps every VCS-evidence repository strictly inside the one approved, snapshot-present, non-traversal path; a nested repo's acknowledgement cannot authorize deleting its parent (covered by test_an_acknowledged_nested_repository_does_not_authorize_its_parent).
  • evidence_adjusted_protections only waives Git-marker protections (vcs-metadata, .git-only baseline-protected-name), never other protected-name classes, and only under a verified repository path.
  • The .git purge is fd-relative and descriptor-anchored throughout: children are opened with O_NOFOLLOW (so a symlink is unlinked, never entered/followed — covered by test_a_link_inside_git_metadata_is_unlinked_not_followed), each child's device is compared against the metadata root's before recursing, and the metadata directory itself is only removed after re-verifying identity through its own descriptor — consistent with the rest of the engine's apply path.
  • Mount, consumer-protection-glob, and readability checks run once before anything is removed and again immediately before the .git contents are purged, fail closed (needs-elevation / filesystem-state-unverified) on an unreadable directory or read error.
  • I raised one candidate (device-only cross-mount check in purge_directory_contents can't distinguish a same-device bind mount from an ordinary subdirectory, so in principle a mount raced into .git mid-purge wouldn't be caught by the device check alone) and had it adversarially verified by a second reviewer pass. Rejected as reportable: establishing a mount visible in this process's mount namespace requires root/CAP_SYS_ADMIN in practice, at which point no privilege boundary is actually crossed (a root-level attacker already has unrestricted filesystem access). Noted here for the record, not raised as a finding.
  • destructive_guard.py's subcommand classification is strictly additive: handoff-apply gets its own ask prompt when enabled and its own deny under the kill switch, alongside the existing apply handling; no existing protection was narrowed, and the new/changed guard paths are covered by new tests (malformed shapes, disabled-guard denial text, decision-record rule names).
  • No instruction-surface deletions: the diff only adds a new guarded command and clarifies docs/safety-model text: it doesn't remove or soften any existing guardrail in CLAUDE.md/AGENTS.md/rules/skill bodies.

GitHub Actions hardening (workflow triggers/permissions/pinning) is out of scope for this lane and this PR doesn't touch .github/workflows.

@claude

claude Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 6m 9s —— View job


Code review complete

  • Gather context (PR diff, CLAUDE.md, changed files)
  • Review hygiene.py (new handoff-apply logic, purge/deletion path)
  • Review destructive_guard.py changes
  • Review engine_grammar.py changes
  • Review docs (README, SKILL.md, safety-model.md, unsupported-platform-handoff.md, CHANGELOG)
  • Review test coverage in test_hygiene.py
  • Post findings

No blocking findings. I read through the new handoff_apply/purge_directory_contents/opaque_contents_blocker logic in hygiene.py, the guard's new mutating-subcommand classification in destructive_guard.py, and the handoff-apply grammar addition in engine_grammar.py, cross-checking each against handoff_verify's existing per-path checks to look for a place where the two could diverge (e.g. the target re-derivation, the .git-metadata carve-out in evidence_adjusted_protections, the tracked_blocker skip for metadata paths, the fd-relative device/mount/glob checks bounding the purge). I didn't find a case where they disagree in a way that would let something be deleted that handoff-verify would contest, or block something a clear verdict should allow.

Specific things I checked and ruled out as bugs:

  • handoff_apply's own target = Path(snapshot["target"]).absolute() (unresolved) vs. resolve_snapshot_target's resolved target used inside the nested handoff_verify call: these only diverge if the path has symlink components, and resolve_snapshot_target already fails closed (raises, uncaught, aborting handoff_apply before any deletion) in that case — fail-closed, not a live bug.
  • The dynamically-built denial message in destructive_guard.py (readonly[:-1] / readonly[-1]) would raise/mis-format if _READONLY_ENGINE_SUBCOMMANDS ever dropped below 2 entries, but that's guarded today (3 entries) and covered by test_every_grammar_subcommand_has_exactly_one_verdict_class / test_disabled_guard_denies_exact_handoff_apply — not an active defect.
  • .git-is-itself-a-mount-point is still caught by hard_protection's existing mount check on the candidate path before the new purge path is ever reached, so purge_directory_contents's device-boundary check is a secondary, not sole, defense.
  • The handoff-apply CLI wiring (--execute/exit codes/report writing) and argument validation (validate_handoff_paths, validate_vcs_evidence) match the existing handoff-verify patterns exactly.

Test coverage (HandoffApplyTests, HandoffApplyPlatformTests, and the new guard tests in test_hygiene.py) is thorough: acknowledged/unacknowledged checkouts, nested repos, drift-after-verdict, mount/glob/unreadable/link cases inside .git, and the full guard classification matrix for the new subcommand. I did not independently re-run bash hygiene.test.sh / scripts/run-ruff.sh / markdownlint-cli2 myself — the PR body's reported results for those are author-claimed, not independently verified by me.

Docs (README, SKILL.md, safety-model.md, unsupported-platform-handoff.md, fan-out-worker-brief.md, CHANGELOG) accurately describe the new Linux route, the purge's bounds, and continue to state the loss warning; I didn't find drift between the prose and the implementation.

…t metadata

The pre-purge scan cannot see an entry created after it ran. The purge now
matches every child against the consumer protection globs as it reaches it and
refuses the removal on a match.

Refs #5178

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@claude

claude Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 5m 8s —— View job


Security review complete

  • Gather context (PR diff vs origin/main, changed files)
  • Read changed files under the security lens
  • Check instruction-surface deletions (CLAUDE.md/AGENTS.md/rules/skills)
  • Hunt for logic flaws, injection, trust-boundary issues in hygiene.py / destructive_guard.py / engine_grammar.py
  • Adversarially verify candidate findings
  • Post final findings

No security issues found.

This PR adds handoff-apply, a Linux-only verify-then-delete route for one operator-acknowledged standalone Git checkout, plus the .git-metadata purge it needs (the snapshot records .git without its descendants). I reviewed the new logic at HEAD 519eab6:

Note: this HEAD already includes the fix for the one P1 Codex raised earlier in this review cycle (recheck consumer-protection globs live during the purge, not just once up front — commit 519eab6). I verified that fix directly rather than re-reporting the pre-fix state:

  • purge_directory_contents now calls protected(directory / name) fresh for every child immediately before it is unlinked or descended into, so an entry created after the pre-purge os.walk (opaque_contents_blocker) is refused rather than deleted. This is exercised directly by test_a_protected_path_created_inside_git_metadata_after_the_scan_is_kept, which injects a matching file via a mocked side effect after the initial scan and confirms the purge stops (completed-with-skips, file survives).
  • Device-crossing is checked live too: each child directory is opened O_NOFOLLOW and its st_dev compared against the metadata root's before recursing, independent of the once-computed mount snapshot — so a mount raced in mid-purge is still caught, not just a mount present at the initial check.
  • Symlinks (and any non-directory entry — sockets, fifos, device nodes) are always routed to os.unlink, never opened/followed, matching is_dir(follow_symlinks=False) semantics; covered by test_a_link_inside_git_metadata_is_unlinked_not_followed.

Other things specifically checked and found sound:

  • Waiver scope. handoff_apply's tracked_waived = evidence.get("status") == "verified" only becomes true when every repository under the approved path passed (cleanly or via accept_unpublished) — a single failing nested repo keeps the whole evidence status "failed", so repository_paths/git_metadata stay empty and no .git gets purge treatment anywhere in the tree. Consistent with handoff_verify's existing per-path containment.
  • Evidence freshness. The verdict is recomputed in-process immediately before deletion (handoff_verify(...) called fresh, not read from a file); path_blockers is recomputed per removal entry with freshly re-fetched mount state (linux_mount_points() inside the loop), so nothing relies on stale state between the root check and each entry's deletion.
  • Grammar/guard wiring. The new handoff-apply Subcommand in engine_grammar.py follows the same shape as the existing handoff-verify/apply entries (required flags first, --data-root external-check, no pattern on --path — matching the precedent already set by handoff-verify's own single-path --path flag, so this isn't a new gap). destructive_guard.py's classification is additive only: handoff-apply gets its own ask prompt and its own kill-switch deny, and no existing subcommand's verdict was narrowed.
  • Docs. safety-model.md, README, SKILL.md and unsupported-platform-handoff.md changes are purely additive/descriptive of the new bounded purge; no existing guardrail language was removed or softened, and the prose matches what the code actually does (mount veto, glob recheck, fail-closed unreadable, link-not-followed, hard-protection names intentionally not applied to .git contents).

GitHub Actions hardening is out of scope for this lane; this PR doesn't touch .github/workflows.

@claude

claude Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Code review in progress

  • Gather context (PR diff, CLAUDE.md, changed files)
  • Review new/changed logic since last reviewed commit (0adbdde → 519eab6)
  • Review hygiene.py handoff-apply / purge logic
  • Review destructive_guard.py and engine_grammar.py changes
  • Review docs and test coverage
  • Post findings

View job run

…t-unpublished-route

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@claude

claude Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 1m 28s —— View job


Security review complete

  • Gather context (PR diff vs origin/main, changed files)
  • Determine what changed since the last reviewed commit (519eab6 → 2597233)
  • Re-check hygiene.py / destructive_guard.py / engine_grammar.py under the security lens
  • Check instruction-surface deletions (CLAUDE.md/AGENTS.md/rules/skills)
  • Adversarially verify any candidate findings
  • Post final findings

No security issues found.

I diffed 519eab6 (the commit a prior review on this PR already covered in full, including the P1 glob-recheck fix) against the current head 2597233 to scope this pass to what's actually new:

git diff 519eab6..2597233 --stat -- plugins/disk-hygiene/

Only two files under plugins/disk-hygiene/ differ, and 2597233 is a clean two-parent merge (519eab6 + 8e525fe, no conflict resolution):

hygiene.py (the handoff_apply / purge_directory_contents / opaque_contents_blocker logic), engine_grammar.py (the handoff-apply subcommand spec), and all touched docs are byte-identical to 519eab6, which a prior pass on this thread already reviewed in full — including the .git-metadata purge's fd-relative walk, O_NOFOLLOW symlink handling, live device/mount/glob rechecks per child (the fix for Codex's P1), waiver scope (tracked_waived requiring every nested repo to have passed), and the guard's additive-only handoff-apply classification.

No instruction-surface deletions: this PR's own changed-file set (11 files) contains no CLAUDE.md/AGENTS.md/rules/skill-body removals, and the merge itself only brings in unrelated additive content from main.

GitHub Actions hardening is out of scope for this lane; this PR doesn't touch .github/workflows.

@claude

claude Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 3m 50s —— View job


Code review complete

  • Gather context (PR diff, CLAUDE.md, changed files)
  • Invoke /review:code-review and follow its instructions
  • Review new/changed logic (hygiene.py, destructive_guard.py, engine_grammar.py)
  • Review docs and test coverage
  • Post findings

No blocking findings. This is the CODE-REVIEW lane (security is scoped out here since a dedicated security lane already ran three completed passes on this branch, the most recent at 519eab6, which matches the disk-hygiene code at this head — the merge to 2597233 only pulls in unrelated main commits, 8e525fe and dab0b0a, and touches no disk-hygiene file).

I diffed origin/main against HEAD per-file (this checkout is shallow and has no merge-base with origin/main, so git diff origin/main...HEAD doesn't resolve; git diff origin/main HEAD -- <paths> does) and read the full new/changed logic:

Specifically checked and found sound, not just the security properties already covered:

  • The confirmed, previously-raised Codex P1 (stale pre-purge scan letting a race-created protected file be deleted) is fixed: purge_directory_contents now calls protected(directory / name) fresh for every child immediately before unlink/descent, exercised by test_a_protected_path_created_inside_git_metadata_after_the_scan_is_kept.
  • handoff_apply_report's status derivation (completed / completed-with-skips / blocked) and main()'s exit-code mapping ({"completed": 0, "completed-with-skips": 4}.get(report["status"], 3)) are internally consistent and covered by the CLI tests.
  • destructive_guard.py's readonly[:-1] / readonly[-1] message-building only works for ≥2 read-only subcommands; today there are 3, and test_every_grammar_subcommand_has_exactly_one_verdict_class pins _READONLY_ENGINE_SUBCOMMANDS/_MUTATING_ENGINE_SUBCOMMANDS against the grammar, so this isn't a live defect, just a fragility that surfaces loudly (an IndexError) rather than silently if it ever regresses.
  • Doc changes (README, SKILL.md, safety-model.md, unsupported-platform-handoff.md) accurately describe the new Linux route, the purge's bounds, and the guard's new ask/kill-switch handling; I didn't find prose that drifted from what the code does (e.g., safety-model.md's "four declared engine command shapes" was correctly changed to drop the stale count now that there are five subcommands).
  • Test coverage for the new logic (acknowledged/unacknowledged/nested/drifted checkouts, mount/glob/unreadable/link cases inside .git, CLI exit codes, guard classification matrix) is thorough and exercises the specific edge cases a reviewer would otherwise ask for.

I did not independently run bash hygiene.test.sh, scripts/run-ruff.sh, or markdownlint-cli2 myself — the PR body's reported results for those are author-claimed, not verified by me here.

…t-unpublished-route

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@claude

claude Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 48s —— View job


Security review complete

  • Gather context (PR diff, CLAUDE.md, changed files)
  • Invoke /review:security-review and follow its instructions
  • Scope to what changed since the last fully-reviewed head (2597233 → 59aa33f)
  • Check instruction-surface deletions (CLAUDE.md/AGENTS.md/rules/skills)
  • Post findings

No security issues found.

This lane has already run four completed security passes on this branch (0adbdde, 519eab6, 2597233, and the in-progress-superseded one at 2597233→2597233), the most recent of which fully reviewed the handoff-apply logic in hygiene.py, the guard classification in destructive_guard.py, and the grammar addition in engine_grammar.py, and reported no issues (the earlier Codex P1 about a stale pre-purge protection scan was fixed in 519eab6 and independently verified).

I diffed that last-reviewed head against the current head to scope this pass to what's actually new:

git diff 2597233729a126a52c3e6cbe4b8dd962ee264acc..HEAD --stat -- plugins/disk-hygiene/

Only three files differ, all documentation/changelog, and none of them are code:

  • CHANGELOG.md — a new 0.29.2 entry recording a probe result about skill-hook lifetime/subagent reach (unrelated to handoff-apply).
  • README.md — rewords the "hook-lifetime caveat" section with a firmer evidentiary basis (a direct Claude Code 2.1.285 probe replacing a producer-reported note) for the same claims: the belt keeps denying for the rest of the session, and the skill-frontmatter belt does not reach subagents.
  • skills/clean/SKILL.md — updates the four-part verification record (Basis/As of/Recheck) backing that same subagent-reach claim, again strengthening rather than weakening it.

None of this narrows or removes a guardrail: the prose still states the belt is session-long and does not reach subagents, now with a more precise citation. hygiene.py, destructive_guard.py, and engine_grammar.py — the files containing the actual handoff-apply verify/delete logic and the .git-metadata purge — are byte-identical to 2597233, which a prior pass on this thread already reviewed in full (fd-relative walk, O_NOFOLLOW symlink handling, live per-child device/mount/glob rechecks, waiver scope requiring every nested repo to pass, additive-only guard classification).

No instruction-surface deletions: this update only reinforces existing hook-lifetime/subagent-reach documentation with better evidence; it removes no guardrail language from CLAUDE.md/AGENTS.md/rules/skill bodies.

GitHub Actions hardening is out of scope for this lane; this PR doesn't touch .github/workflows.

@claude

claude Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 1m 18s —— View job


Code review complete

  • Gather context (PR diff, CLAUDE.md, changed files)
  • Invoke /review:code-review and follow its instructions
  • Determine what changed since the last reviewed head (2597233 → 59aa33f)
  • Post findings

Skip gate: this head already has a successful review from this lane that still applies.

I diffed the previously-reviewed head (2597233729a126a52c3e6cbe4b8dd962ee264acc, covered in full by the code-review pass at 2026-09-30T05:42:56Z) against the current HEAD (59aa33fa8e00a0b19f8f1a9714744c00f0b4c2a4):

git diff 2597233..59aa33f --stat -- plugins/disk-hygiene/

Only three files differ, and all three changes come from an unrelated PR (#5565, docs(disk-hygiene): state belt lifetime and subagent reach from a 2.1.285 probe) pulled in by this merge commit's second parent (84548fe), not authored for this PR:

  • CHANGELOG.md — adds the 0.29.2 entry for the belt-lifetime probe.
  • README.md — rewrites the "Hook lifetime" caveat with the probe's findings.
  • skills/clean/SKILL.md — updates the four-part verification record's basis/as-of/recheck fields for the same probe.

None of this touches handoff-apply, the .git-metadata purge, destructive_guard.py, engine_grammar.py, or test_hygiene.py — hygiene.py, destructive_guard.py, engine_grammar.py, and test_hygiene.py are byte-identical to 2597233, which a prior code-review pass on this thread already reviewed in full (the handoff_apply/purge_directory_contents/opaque_contents_blocker logic, the guard's additive-only handoff-apply classification, the grammar spec, and the new test coverage) with no blocking findings, after the earlier Codex P1 (stale pre-purge protection-glob scan) was fixed in 519eab6 and verified.

No new findings for this PR's actual scope. The only content since the last full review is doc prose belonging to a different, already-merged PR that arrived here as a side effect of merging origin/main into this branch.

I did not independently run bash hygiene.test.sh, scripts/run-ruff.sh, or markdownlint-cli2 — the PR body's reported results for those remain author-claimed, not verified by me.

…t-unpublished-route

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@claude

claude Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 2m 8s —— View job


Code review complete

  • Gather context (PR diff vs origin/main, changed files)
  • Invoke /review:code-review and follow its instructions
  • Determine what changed since the last reviewed head (59aa33f → 957242d)
  • Post findings

Skip gate: this head already has a successful review from this lane that still applies.

957242d is a merge commit (Merge remote-tracking branch 'origin/main' into feat/5178-linux-accept-unpublished-route), not a new authored commit on this branch. I diffed the previously fully-reviewed head (59aa33fa8e00a0b19f8f1a9714744c00f0b4c2a4, covered by the code-review pass at 2026-09-30T06:18:21Z) against current HEAD:

git diff 59aa33f 957242d --stat -- plugins/disk-hygiene/

and cross-checked it against this PR's actual diff vs origin/main (the 11-file, +1069/-65 set in the PR metadata). Everything that changed between those two heads is unrelated content pulled in by the merge from origin/main, none of it authored on this branch:

  • Policy overlay v2 (hygiene.py, test_hygiene.py, evals.json, policy-overlay.schema.json, SKILL.md, safety-model.md, README.md) — merged in from #5542 (be44d87). Confirmed the hygiene.py hunks all land before line ~3713 (load_policy/apply_policy_overlay/scan_tree/preview), nowhere near handoff_apply/purge_directory_contents/opaque_contents_blocker at L4043–L4618, and the new test_hygiene.py hunks are all inside HygieneTests/ScanOutputVerbosityTests/OsAutocleanAdvisoryTests, not HandoffApplyTests.
  • PowerShell guardrail fix (#5532, d36f47f) — destructive_guard.py/engine_grammar.py are byte-identical between 59aa33f and 957242d (absent from the --stat output entirely), so the handoff-apply grammar spec and guard classification a prior pass already reviewed in full are untouched.
  • unsupported-platform-handoff.md's only hunk visible in that two-head diff is main's pre-existing elevation: uac-prompt carve-out (already on origin/main before this branch merged it — verified via git show origin/main:...), not a change to this PR's own Linux-route carve-out at L108–L110, which is identical to what was reviewed before.

So the actual handoff-apply/purge logic, the guard wiring, the grammar spec, and this PR's own doc/test additions are unchanged since the last full pass — no new review surface to cover.

No new findings for this PR's actual scope. I did not independently run bash hygiene.test.sh, scripts/run-ruff.sh, or markdownlint-cli2 — the PR body's reported results remain author-claimed, not verified by me.

@claude

claude Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 1m 52s —— View job


Security review complete

  • Gather context (PR diff vs origin/main, changed files)
  • Determine what changed since the last fully-reviewed head
  • Re-check for any new security-relevant surface
  • Check instruction-surface deletions (CLAUDE.md/AGENTS.md/rules/skills)
  • Post findings

Skip gate: this head already has a successful security review that still applies.

Five security passes already ran on this branch (0adbdde, 519eab6, 2597233, 59aa33f, and this one at 957242d), the most recent full ones covering hygiene.py's handoff_apply/purge_directory_contents/opaque_contents_blocker, the guard classification in destructive_guard.py, and the grammar addition in engine_grammar.py, with no issues found (the earlier Codex P1 — a stale pre-purge protection-glob scan — was fixed in 519eab6 and independently verified).

I confirmed the current head (957242d, a merge of origin/main into this branch) introduces no new PR-scoped changes to review:

  • git diff origin/main HEAD --stat -- plugins/disk-hygiene/ shows exactly the same 11 files with the same +1069/-65 totals as the PR's original stated diff — this branch's actual contribution against origin/main is unchanged.
  • hygiene.py, destructive_guard.py, and engine_grammar.py are each byte-identical, diffed against origin/main, to what the 2597233/59aa33f passes already reviewed in full (confirmed via direct diff comparison; the apply_rules/policy_rule/overlay-v2 code visible when comparing 2597233..957242d directly belongs to an unrelated feature — policy-overlay v2, PR feat(disk-hygiene): policy overlay v2 with preselect rules, age thresholds and an elevation opt-in #5542 — that landed on main and was pulled in by this merge; it does not appear in this PR's diff against origin/main since it's already present there).
  • test_hygiene.py's PR-scoped diff is still exactly +519/-0, matching the prior fully-reviewed head.

No instruction-surface deletions: this PR's changed-file set contains no CLAUDE.md/AGENTS.md/rules/skill-body removals, and the merge only brings in unrelated additive content from main.

No new security findings for this PR's scope. GitHub Actions hardening is out of scope for this lane; this PR doesn't touch .github/workflows.

I did not independently run bash hygiene.test.sh, scripts/run-ruff.sh, or markdownlint-cli2 — the PR body's reported results for those remain author-claimed, not verified by me.

…y model

SKILL.md sat at the 500-line hard cap after the merge with main. The Linux
command block moves next to the standalone checkout evidence it consumes, and
the skill bullet links to it.

Refs #5178

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@claude

claude Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 50s —— View job


Code review complete

  • Gather context (PR diff, CLAUDE.md, changed files)
  • Invoke /review:code-review and follow its instructions
  • Determine what changed since the last reviewed head (957242d → c36b04d)
  • Post findings

Skip gate: this head already has a successful review from this lane that still applies.

c36b04d is a pure docs-reorg commit (docs(disk-hygiene): move the handoff-apply command block to the safety model), authored to keep SKILL.md under its 500-line hard cap after the #5542 merge. I diffed it against the previously fully-reviewed head (957242d, covered by the code-review pass at 2026-09-30T06:30:43Z):

git show --stat c36b04d

Five files changed, none of them the logic this lane already reviewed in full:

  • SKILL.md — the Linux handoff-apply command block moves out of section 6 into safety-model.md, next to the standalone-checkout evidence it consumes; the throwaway-checkout bullet now links to it instead of duplicating the command inline.
  • safety-model.md — receives the moved command block verbatim, placed directly under the "Standalone Git checkout evidence" section (#standalone-git-checkout-evidence, matching the anchor SKILL.md now links to).
  • unsupported-platform-handoff.md — updates one cross-reference (SKILL.md section 6 → command in safety-model.md) to match the move.
  • CHANGELOG.md — rewords the existing 0.31.0 entry to describe the new doc location instead of the old one.
  • test_hygiene.py — updates HandoffVerifyTests to check for the new link text in SKILL.md and adds an assertion that safety-model.md itself contains the handoff-apply --execute command block.

I confirmed the move is self-consistent: SKILL.md's new link target (reference/safety-model.md#standalone-git-checkout-evidence) matches the heading anchor in safety-model.md, the command block's content is byte-identical to what was removed from SKILL.md, and SKILL.md is now 499 lines (under the stated 500-line cap, matching the commit's stated motivation).

hygiene.py, destructive_guard.py, and engine_grammar.py — the files containing the actual handoff-apply/purge logic and the guard/grammar wiring — are untouched by this commit; they're byte-identical to 957242d, which a prior pass on this thread already reviewed in full (the handoff_apply/purge_directory_contents/opaque_contents_blocker logic, the guard's additive-only handoff-apply classification, the grammar spec, and the test coverage), with the earlier Codex P1 (stale pre-purge protection-glob scan) fixed in 519eab6 and independently verified.

No new findings for this PR's scope. I did not independently run bash hygiene.test.sh, scripts/run-ruff.sh, or markdownlint-cli2 — the PR body's reported results remain author-claimed, not verified by me.

@claude

claude Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 44s —— View job


Security review complete

  • Gather context (PR diff vs origin/main, changed files, prior review history on this thread)
  • Invoke /review:security-review and follow its instructions exactly
  • Determine what changed since the last fully-reviewed head (957242d → c36b04d)
  • Check instruction-surface deletions (CLAUDE.md/AGENTS.md/rules/skills)
  • Post findings

Skip gate: this head already has a successful security review that still applies.

Six security passes have already run on this branch, the most recent full one at 957242d covering hygiene.py's handoff_apply/purge_directory_contents/opaque_contents_blocker, the guard classification in destructive_guard.py, and the grammar addition in engine_grammar.py, with no issues found.

git diff 957242d c36b04d --stat shows the only delta is:

  • plugins/disk-hygiene/CHANGELOG.md, skills/clean/SKILL.md, skills/clean/reference/safety-model.md, skills/clean/reference/unsupported-platform-handoff.md — pure documentation relocation. The handoff-apply --execute command block moves from SKILL.md §6 to safety-model.md, with cross-references in SKILL.md and unsupported-platform-handoff.md updated to point at the new location. I diffed the moved block line-for-line: the command itself, the "any verdict but clear removes nothing," and the "confirm the guard's ask only for that path" warning all move intact — nothing was dropped, narrowed, or softened. This is a relocation, not an instruction-surface deletion under the register's consequence test.
  • skills/clean/scripts/test_hygiene.py — updates the HandoffVerifyTests string assertions to match the relocated doc text (checks the new cross-reference phrasing in SKILL.md and adds an assertion that safety-model.md contains the handoff-apply --execute command), mirroring the doc move with no logic change.

hygiene.py, destructive_guard.py, and engine_grammar.py — the files containing the actual handoff-apply verify/delete logic, the .git-metadata purge, and the guard/grammar wiring — are byte-identical to 957242d, which prior passes on this thread already reviewed in full (fd-relative walk, O_NOFOLLOW symlink handling, live per-child device/mount/glob rechecks fixing Codex's P1, waiver scope requiring every nested repo to pass, additive-only guard classification).

No new security findings for this PR's scope. No instruction-surface deletions. GitHub Actions hardening is out of scope for this lane; this PR doesn't touch .github/workflows.

I did not independently run bash hygiene.test.sh, scripts/run-ruff.sh, or markdownlint-cli2 — the PR body's reported results for those remain author-claimed, not verified by me.

Renumber to disk-hygiene 0.33.0 above main's 0.32.0 and carry main's catalog
subcommand through the guard's read-only set and the docs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@claude

claude Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 1m 46s —— View job


Security review complete

  • Gather context (PR diff vs origin/main, changed files, prior review history on this thread)
  • Determine what changed since the last fully-reviewed head (c36b04d → 6e9c470)
  • Re-check any new security-relevant surface
  • Check instruction-surface deletions (CLAUDE.md/AGENTS.md/rules/skills)
  • Post final findings

No security issues found.

Seven security passes have already run on this branch. The most recent full one, at 957242d/c36b04d, covered hygiene.py's handoff_apply/purge_directory_contents/opaque_contents_blocker, the guard classification in destructive_guard.py, and the grammar addition in engine_grammar.py, with no issues found — including the earlier Codex P1 (stale pre-purge protection-glob scan), fixed in 519eab6 and independently verified.

I confirmed this PR's actual scope against origin/main is unchanged: git diff origin/main HEAD --stat -- plugins/disk-hygiene/ still shows the same 11 files with the same +1068/-64 totals reported in the PR body. The current head (6e9c470) is a merge of origin/main into this branch, so I diffed the previously-reviewed head against it to find what's actually new:

git diff c36b04d 6e9c470 --stat -- plugins/disk-hygiene/

Fifteen files differ, but every one of them is unrelated content pulled in from origin/main (the disk-image-protection PR #5531, an investigated_catalog.py/catalog subcommand feature, and unrelated docs), not authored on this branch:

  • hygiene.py (+295/-…) — I read every hunk. All are from feat(disk-hygiene): protect virtual-disk images and report the volume an attached image backs #5531's virtual-disk-image feature (is_virtual_disk_name, virtual_disk_attachment, hard_protection's new virtual-disk/attached-virtual-disk:* reasons, metadata's size-qualifier), the new catalog/investigated_catalog plumbing in main(), and one true cross-cutting change: preview() and handoff_verify() now skip a redundant hard_protection() call when name == relative (avoiding a second virtual-disk-attach probe on the candidate path). I traced this: candidate_protections is computed once before the loop (L4225) and already merged into contested (L4317) before the for name in expected_paths loop begins, so skipping the recompute for name == relative inside the loop drops no protection — it was already accounted for. handoff_apply, purge_directory_contents, opaque_contents_blocker, and evidence_adjusted_protections — the actual delete-path logic this PR owns — are byte-identical to the last fully-reviewed head.
  • destructive_guard.py — adds "catalog" to _READONLY_ENGINE_SUBCOMMANDS (from the unrelated catalog subcommand). _MUTATING_ENGINE_SUBCOMMANDS = frozenset({"apply", "handoff-apply"}) is untouched; handoff-apply's ask/kill-switch handling is unchanged.
  • engine_grammar.py — adds the new catalog Subcommand spec; the handoff-apply spec is unchanged.
  • README.md/safety-model.md diffs since c36b04d contain no mention of handoff-apply, purge, or .git metadata — confirmed by grep; they're all disk-image-feature prose.
  • evals.json, baseline-policy.json, scan-flags.md, investigated_catalog.py, test_investigated_catalog.py are entirely new/unrelated files from the merge, outside this PR's own diff against origin/main.

No instruction-surface deletions: git diff origin/main HEAD --stat -- CLAUDE.md AGENTS.md .claude/rules/ is empty for this PR's scope.

No new security findings. GitHub Actions hardening is out of scope for this lane; this PR doesn't touch .github/workflows.

I did not independently run bash hygiene.test.sh, scripts/run-ruff.sh, or markdownlint-cli2 — the PR body's reported results for those remain author-claimed, not verified by me.

@claude

claude Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 1m 54s —— View job


Code review complete

  • Gather context (PR diff vs origin/main, changed files)
  • Invoke /review:code-review and follow its instructions
  • Determine what changed since the last reviewed head (c36b04d → 6e9c470)
  • Review any new/changed logic
  • Post findings

Skip gate: this head already has a successful review from this lane that still applies.

6e9c470 is a merge of origin/main into this branch (bringing in #5531, virtual-disk image protection, and an unrelated catalog subcommand feature). I confirmed this PR's actual contribution is unchanged:

git diff origin/main HEAD --stat -- plugins/disk-hygiene/

still shows the same 11 files as the PR's stated diff (+1068/-64), and I diffed the previously fully-reviewed head (c36b04d, covered by the code-review pass at 2026-09-30T06:43:43Z) against current HEAD directly:

  • hygiene.py: the only PR-adjacent hunk is in handoff_verify, where the merged-in virtual-disk feature adds a name != relative guard to avoid re-probing the candidate's own protections. This is pre-existing on origin/main (byte-identical there), not authored by this PR — git diff origin/main HEAD shows zero delta to handoff_verify. The functions this PR actually owns — handoff_apply, purge_directory_contents, opaque_contents_blocker — are byte-identical to c36b04d, which a prior pass already reviewed in full (fd-relative walk, O_NOFOLLOW symlink handling, live per-child device/mount/glob rechecks fixing Codex's P1, waiver scope requiring every nested repo to pass).
  • destructive_guard.py / engine_grammar.py: byte-identical to c36b04d. The _READONLY_ENGINE_SUBCOMMANDS/_MUTATING_ENGINE_SUBCOMMANDS refactor this PR made was already reviewed at 2597233; _MUTATING_ENGINE_SUBCOMMANDS = frozenset({"apply", "handoff-apply"}) is untouched by the merge, and the newly-merged catalog subcommand only adds to the read-only set.
  • test_hygiene.py: the new hunks since c36b04d (test_disk_image_candidate_is_probed_once, test_windows_attach_probe_never_calls_a_non_image_attached, the catalog entries in the grammar-authority matrices) all belong to the merged-in virtual-disk/catalog features, not to HandoffApplyTests/HandoffApplyPlatformTests, which are unchanged.
  • Docs (README, SKILL.md, safety-model.md, unsupported-platform-handoff.md, CHANGELOG) — unchanged from what was already reviewed at c36b04d.

No new findings for this PR's scope. The one prior finding on this thread (Codex P1, stale pre-purge protection-glob scan) was fixed in 519eab6 and independently verified. I did not independently run bash hygiene.test.sh, scripts/run-ruff.sh, or markdownlint-cli2 — the PR body's reported results for those remain author-claimed, not verified by me.

Keep disk-hygiene 0.34.0 above main's 0.33.1 with this PR's entry on top.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@claude

claude Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 57s —— View job


Security review complete

  • Gather context (PR diff vs origin/main, changed files, prior review history on this thread)
  • Invoke /review:security-review and follow its instructions exactly
  • Determine what changed since the last fully-reviewed head (8870859 → 083973c)
  • Re-check any new security-relevant surface
  • Check instruction-surface deletions (CLAUDE.md/AGENTS.md/rules/skills)
  • Post final findings

Skip gate: this head already has a successful security review that still applies.

This PR's actual scope against origin/main is unchanged: git diff origin/main HEAD --stat -- plugins/disk-hygiene/ still shows exactly the same 11 files with the same +1068/-64 totals reported in the PR body.

I diffed the previously fully-reviewed head (8870859, security pass at 2026-09-30T16:07:42Z) against the current head 083973c:

git diff 8870859 083973c37a35857f254d991cec87ffe75f783a93 --stat -- plugins/disk-hygiene/

Three files differ, and none touch handoff-apply or the .git-metadata purge:

plugins/disk-hygiene/skills/clean/scripts/hygiene.py and plugins/disk-hygiene/lib/engine_grammar.py — the files containing the actual handoff-apply verify/delete logic, the .git-metadata purge, and the grammar spec — are byte-identical to 8870859 (confirmed via empty diff output). That head, and the ones before it, already received ten prior security passes on this thread covering the fd-relative walk, O_NOFOLLOW symlink handling, live per-child device/mount/glob rechecks (the fix for Codex's P1), waiver scope requiring every nested repo to pass, and the guard's additive-only handoff-apply classification — all found sound.

No instruction-surface deletions: git diff origin/main HEAD --stat -- CLAUDE.md AGENTS.md .claude/rules/ is empty for this PR's scope.

No new security findings. GitHub Actions hardening is out of scope for this lane; this PR doesn't touch .github/workflows.

I did not independently run bash hygiene.test.sh, scripts/run-ruff.sh, or markdownlint-cli2 — the PR body's reported results for those remain author-claimed, not verified by me.

@github-actions

github-actions Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

PR body contract — issue linkage

This PR body conforms to the issue-linkage contract. Nothing to do.

@claude

claude Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 1m 15s —— View job


Code review complete

  • Gather context (PR diff vs origin/main, changed files, prior review history on this thread)
  • Invoke /review:code-review and follow its instructions exactly
  • Determine what changed since the last reviewed head (8870859 → 083973c)
  • Review new/changed logic
  • Post findings

Skip gate: this head already has a successful review from this lane that still applies.

I confirmed this PR's actual contribution against origin/main is unchanged: git diff origin/main HEAD --stat -- plugins/disk-hygiene/ still shows exactly the same 11 files with the same +1068/-64 totals reported in the PR body.

083973c is a merge of origin/main into this branch. I diffed the previously fully-reviewed head (8870859, code-review pass at 2026-09-30T16:07:38Z) against the current head:

git diff 8870859 HEAD --stat -- plugins/disk-hygiene/

Only three files differ:

  • destructive_guard.py (+154/-14) — this is the "PowerShell lane allows engine names in string data" fix (commit e983c39, #5521), pulled in from origin/main by this merge, not authored on this branch. It adds _powershell_without_string_data, _POWERSHELL_EXECUTORS, _POWERSHELL_TYPOGRAPHIC_QUOTES, etc., and wires them into _engine_gate_relevant/powershell_decision. None of it touches _READONLY_ENGINE_SUBCOMMANDS, _MUTATING_ENGINE_SUBCOMMANDS, or _MUTATION_PROMPTS — confirmed via git diff origin/main HEAD -- destructive_guard.py, which shows this PR's own contribution to the file is still only that refactor (handoff-apply added to _MUTATING_ENGINE_SUBCOMMANDS, its own ask prompt and kill-switch deny message), already reviewed in full at 2597233.
  • test_hygiene.py (+44) — two new tests (test_powershell_engine_name_in_string_data_defers, test_powershell_executable_string_naming_engine_still_denies) for that same merged-in PowerShell fix, not for HandoffApplyTests/HandoffApplyPlatformTests.
  • CHANGELOG.md (+6) — the 0.33.1 entry for the same fix.

hygiene.py and engine_grammar.py — the files containing handoff_apply, purge_directory_contents, opaque_contents_blocker, evidence_adjusted_protections, and the handoff-apply grammar spec — are byte-identical to 8870859 (confirmed: git diff 8870859 HEAD -- hygiene.py engine_grammar.py produces no output). That head, and the ones before it, were already reviewed in full by this lane across ten prior passes: the fd-relative .git-metadata purge (O_NOFOLLOW symlink handling, live per-child device/mount/glob rechecks fixing the earlier Codex P1), waiver scope requiring every nested repository to pass before any .git gets purge treatment, and the guard's additive-only handoff-apply classification — with no blocking findings.

No instruction-surface deletions: git diff origin/main HEAD --stat -- CLAUDE.md AGENTS.md .claude/rules/ is empty for this PR's scope.

No new findings for this PR's scope. I did not independently run bash hygiene.test.sh, scripts/run-ruff.sh, or markdownlint-cli2 — the PR body's reported results for those remain author-claimed, not verified by me.

Bump disk-hygiene to 0.35.0 above main's 0.34.0. The mutation prompt table maps
each mutating subcommand to a reason function so apply keeps main's plan-listing
reason and handoff-apply keeps its own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@claude

claude Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 1m 23s —— View job


Security review complete

  • Gather context (PR diff vs origin/main, changed files, prior review history on this thread)
  • Invoke /review:security-review and follow its instructions exactly
  • Determine what changed since the last fully-reviewed head (083973c → 80fdbc6)
  • Re-check any new security-relevant surface
  • Check instruction-surface deletions (CLAUDE.md/AGENTS.md/rules/skills)
  • Post final findings

Skip gate: this head already has a successful security review that still applies.

This PR's actual scope against origin/main is unchanged: git diff origin/main HEAD --stat -- plugins/disk-hygiene/ still shows exactly the same 11 files with the same +1068/-64 totals reported in the PR body.

I diffed the previously fully-reviewed head (083973c, security pass at 2026-09-30T16:24:43Z) against the current head 80fdbc6:

git diff 083973c37a35857f254d991cec87ffe75f783a93 80fdbc680db05dda0b489f481107e98d700b410e --stat -- plugins/disk-hygiene/

Eight files differ. All of it is content merged in from origin/main (PR #5528, "repeatable handoff-verify --path, self-describing ask reasons, allow-rule gotcha"), not authored on this branch, and none of it touches the handoff-apply verify/delete path or the .git-metadata purge this PR owns:

  • hygiene.py — the only hunk makes handoff-verify's --path repeatable (args.path becomes a list). handoff-apply's own call site ([args.path]) is untouched, and I confirmed in engine_grammar.py that handoff-apply's --path flag stays required=True and non-repeatable — the two subcommands share no flag object, so the handoff-verify change can't desync handoff-apply's single-path wrapping. handoff_apply, purge_directory_contents, opaque_contents_blocker, and evidence_adjusted_protections (this PR's own code, ~L4340–4801) are byte-identical to 083973c.
  • destructive_guard.py (+175) — the largest hunk, but it's the merged-in "self-describing ask reasons" feature: apply's prompt now reads its --plan/--snapshot files and lists the tier and paths it will delete (all displayed text passed through a new _display_text that escapes control characters, so a newline embedded in a path can't restructure the prompt); the PowerShell mutation prompt similarly lists path-shaped literals it finds, explicitly caveated as informational-only and never feeding a verdict. Critically, _MUTATION_PROMPTS["handoff-apply"] is now lambda _command: _HANDOFF_APPLY_ASK_REASON — same static text as before, verified byte-identical to the prior _MUTATION_PROMPTS["handoff-apply"] string, just wrapped to fit the new per-command-kind signature. _READONLY_ENGINE_SUBCOMMANDS, _MUTATING_ENGINE_SUBCOMMANDS = frozenset({"apply", "handoff-apply"}), and the kill-switch/ask classification for handoff-apply are unchanged.
  • test_hygiene.py (+255) — grepped the diff: new tests are all in HandoffVerifyTests (repeated --path) and GuardTests (_apply_command, self-describing prompt text); no HandoffApplyTests/HandoffApplyPlatformTests changes.
  • SKILL.md, reference/unsupported-platform-handoff.md, CHANGELOG.md, plugin.json — doc/version updates describing the same repeatable---path and self-describing-prompt features. One new bullet in SKILL.md's Gotchas section is additive hardening documentation ("a permissions.allow rule cannot remove the deletion prompts... Auto mode also drops broad interpreter allow rules"), not a guardrail removal.

No instruction-surface deletions: git diff origin/main HEAD --stat -- CLAUDE.md AGENTS.md .claude/rules/ is empty for this PR's scope, and nothing in the above softens or removes an existing control.

No new security findings. This is the eleventh security pass on this branch; the most recent full ones (957242d/c36b04d/ae564da/8870859/083973c) already covered handoff-apply's verify/delete logic in hygiene.py (fd-relative .git-metadata purge, O_NOFOLLOW symlink handling, live per-child device/mount/glob rechecks fixing the earlier Codex P1, waiver scope requiring every nested repo to pass) and the guard's additive-only classification in destructive_guard.py / engine_grammar.py, all found sound.

GitHub Actions hardening is out of scope for this lane; this PR doesn't touch .github/workflows.

I did not independently run bash hygiene.test.sh, scripts/run-ruff.sh, or markdownlint-cli2 — the PR body's reported results for those remain author-claimed, not verified by me.

@claude

claude Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 1m 10s —— View job


Code review complete

  • Gather context (PR diff vs origin/main, changed files, prior review history on this thread)
  • Invoke /review:code-review and follow its instructions exactly
  • Determine what changed since the last reviewed head (083973c → 80fdbc6)
  • Review new/changed logic
  • Post findings

Skip gate: this head already has a successful review from this lane that still applies.

I confirmed this PR's actual contribution against origin/main is unchanged: git diff origin/main HEAD --stat -- plugins/disk-hygiene/ still shows the same 11 files with the same +1068/-64 totals reported in the PR body.

80fdbc6 is a merge of origin/main into this branch, pulling in #5528 ("repeatable handoff-verify --path, self-describing ask reasons, allow-rule gotcha"). I diffed the previously fully-reviewed head (083973c, code-review pass at 2026-09-30T16:25:49Z) against current HEAD:

git diff 083973c 80fdbc6 --stat -- plugins/disk-hygiene/

Eight files differ, and every change belongs to that merged-in, already-landed PR, not to this branch's own handoff-apply feature:

  • engine_grammar.py — handoff-verify's --path flag gains repeatable=True. handoff-apply's own --path flag (L287-300) is untouched: still required=True, still single-use, with its "one exact approved path per call" comment intact.
  • destructive_guard.py — adds self-describing ask reasons: _apply_ask_reason reads the plan file to list the tier/paths apply is about to delete, and the PowerShell mutation prompt now lists path-shaped literals it can parse out of the command. _MUTATION_PROMPTS becomes a dict of callables; handoff-apply's entry is lambda _command: _HANDOFF_APPLY_ASK_REASON, i.e. the exact same static text as before, just wrapped to fit the new callable shape. I traced the one call site (_MUTATION_PROMPTS[command_kind](command) at L2794) — command is in scope, and handoff-apply's ask verdict and prompt text are unchanged.
  • hygiene.py — one hunk, in main()'s handoff-verify CLI branch (args.path now a list, passed through unchanged), not in handoff_apply/purge_directory_contents/opaque_contents_blocker.
  • SKILL.md, unsupported-platform-handoff.md, CHANGELOG.md, test_hygiene.py — doc/test updates for the same repeatable---path and self-describing-prompt feature; I grepped the test diff for handoff and found only handoff-verify/handoff_verify cases, no HandoffApply* change.
  • .claude-plugin/plugin.json — version bump 0.34.0 → 0.35.0, matching the new CHANGELOG.md heading.

README.md, reference/safety-model.md, and reference/fan-out-worker-brief.md are byte-identical to 083973c (absent from the stat output). handoff_apply, purge_directory_contents, opaque_contents_blocker, and evidence_adjusted_protections in hygiene.py — the functions this PR actually owns — are unchanged since the last fully-reviewed head, which prior passes on this thread already covered in full (fd-relative .git-metadata purge, O_NOFOLLOW symlink handling, live per-child device/mount/glob rechecks fixing the earlier Codex P1, waiver scope requiring every nested repository to pass, additive-only guard classification for handoff-apply).

No new findings for this PR's scope. I did not independently run bash hygiene.test.sh, scripts/run-ruff.sh, or markdownlint-cli2 — the PR body's reported results for those remain author-claimed, not verified by me.

Keep disk-hygiene 0.35.0 above main's 0.34.1 with this PR's entry on top.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@claude

claude Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 36s —— View job


Code review complete

  • Gather context (PR diff vs origin/main, changed files, prior review history on this thread)
  • Invoke /review:code-review and follow its instructions exactly
  • Determine what changed since the last reviewed head (80fdbc6 → bac7da7)
  • Review new/changed logic
  • Post findings

Skip gate: this head already has a successful review from this lane that still applies.

I confirmed this PR's actual contribution against origin/main is unchanged: git diff origin/main HEAD --stat -- plugins/disk-hygiene/ still shows the same 11 files with the same +1068/-64 totals reported in the PR body.

bac7da7 is a merge of origin/main into this branch. I diffed the previously fully-reviewed head (80fdbc6, code-review pass at 2026-09-30T16:43:13Z) against the current head:

git diff 80fdbc6 bac7da7 --stat -- plugins/disk-hygiene/

Only two files differ, and both are unrelated content pulled in from origin/main (#5578, "do not empty the bin after a recycle", fixing #4228):

  • CHANGELOG.md — new 0.34.1 entry for that fix.
  • reference/unsupported-platform-handoff.md — adds a sentence to the gated manual lane's step 2 ("After a recycle, do not empty the Recycle Bin or Trash..."). This is in the manual (non-handoff-apply) lane's instructions, not the Linux handoff-apply route this PR owns, and it tightens guidance rather than removing any.

hygiene.py, destructive_guard.py, engine_grammar.py, and test_hygiene.py — the files containing the handoff-apply/purge logic this PR owns — are byte-identical to 80fdbc6 (confirmed via empty diff output). That head, and the ones before it, received twelve prior review passes on this thread (code-review and security-review alternating) that already covered the handoff_apply/purge_directory_contents/opaque_contents_blocker logic in full: the fd-relative .git-metadata purge, O_NOFOLLOW symlink handling, live per-child device/mount/glob rechecks (fixing the one confirmed Codex P1, a stale pre-purge protection-glob scan, in 519eab6), waiver scope requiring every nested repository to pass before any .git gets purge treatment, and the guard's additive-only handoff-apply classification — with no blocking findings.

No instruction-surface deletions: git diff origin/main HEAD --stat -- CLAUDE.md AGENTS.md .claude/rules/ is empty for this PR's scope.

No new findings for this PR's scope. I did not independently run bash hygiene.test.sh, scripts/run-ruff.sh, or markdownlint-cli2 — the PR body's reported results for those remain author-claimed, not verified by me.

@claude

claude Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 37s —— View job


Security review complete

  • Gather context (PR diff vs origin/main, changed files, prior review history on this thread)
  • Invoke /review:security-review and follow its instructions exactly
  • Determine what changed since the last fully-reviewed head (80fdbc6 → bac7da7)
  • Re-check any new security-relevant surface
  • Check instruction-surface deletions (CLAUDE.md/AGENTS.md/rules/skills)
  • Post final findings

Skip gate: this head already has a successful security review that still applies.

This PR's actual scope against origin/main is unchanged: git diff origin/main HEAD --stat -- plugins/disk-hygiene/ still shows exactly the same 11 files with the same +1068/-64 totals reported in the PR body.

I diffed the previously fully-reviewed head (80fdbc6, security pass at 2026-09-30T16:43:11Z) against the current head bac7da7:

git diff 80fdbc6 bac7da7 --stat -- plugins/disk-hygiene/

Only two files differ, both pure documentation, and both come from an unrelated, already-merged PR (#5578, "docs(disk-hygiene): do not empty the bin after a recycle", fixing #4228) pulled in by this merge commit, not authored on this branch:

This is a guardrail addition, not a deletion or softening — it closes a gap where a container-wide empty-after-recycle could have silently defeated reversibility, and it doesn't touch this PR's own handoff-apply route.

hygiene.py, destructive_guard.py, engine_grammar.py, and test_hygiene.py — the files containing the handoff-apply verify/delete logic, the .git-metadata purge, and the guard/grammar wiring this PR owns — are byte-identical to 80fdbc6 (confirmed: empty diff output). That head was already reviewed in full across twelve prior security passes on this thread (fd-relative .git-metadata purge, O_NOFOLLOW symlink handling, live per-child device/mount/glob rechecks fixing the earlier Codex P1, waiver scope requiring every nested repository to pass before any .git gets purge treatment, additive-only handoff-apply classification in the guard), with no issues found.

No instruction-surface deletions: git diff origin/main HEAD --stat -- CLAUDE.md AGENTS.md .claude/rules/ is empty for this PR's scope, and the one doc change since the last pass strengthens rather than weakens a guardrail.

No new security findings. GitHub Actions hardening is out of scope for this lane; this PR doesn't touch .github/workflows.

I did not independently run bash hygiene.test.sh, scripts/run-ruff.sh, or markdownlint-cli2 — the PR body's reported results for those remain author-claimed, not verified by me.

Keep disk-hygiene 0.35.0 above main's 0.34.2 with this PR's entry on top.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@claude

claude Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 39s —— View job


Security review complete

  • Gather context (PR diff vs origin/main, changed files, prior review history on this thread)
  • Invoke /review:security-review and follow its instructions exactly
  • Determine what changed since the last fully-reviewed head (bac7da7 → b76ae7b)
  • Re-check any new security-relevant surface
  • Check instruction-surface deletions (CLAUDE.md/AGENTS.md/rules/skills)
  • Post final findings

Skip gate: this head already has a successful security review that still applies.

This PR's actual scope against origin/main is unchanged: git diff origin/main HEAD --stat -- plugins/disk-hygiene/ still shows exactly the same 11 files with the same +1068/-64 totals reported in the PR body.

b76ae7b is a merge of origin/main into this branch. I diffed the previously fully-reviewed head (bac7da7, security pass at 2026-09-30T16:59:08Z) against the current head:

git diff bac7da7 b76ae7b --stat -- plugins/disk-hygiene/

Four files differ, and every change is content merged in from origin/main — a new baseline-policy hint for Windows reserved device-name files on Linux (nul, con, prn, aux, com1-com9, lpt1-lpt9) plus a SKILL.md clarification about os_autoclean's null recommendation on Linux — not authored on this branch and unrelated to handoff-apply:

  • plugins/disk-hygiene/skills/clean/reference/baseline-policy.json (+108) — adds windows-device-name-* hint entries, all confidence_ceiling: "low", os: ["linux"], entry_types: ["file"]. These are advisory hints only (they affect what clean's scan suggests, not what handoff-apply deletes) and don't touch the .git-metadata purge, protection classes, or waiver logic this PR owns.
  • plugins/disk-hygiene/CHANGELOG.md — the 0.34.2 entry for the same hint feature.
  • plugins/disk-hygiene/skills/clean/SKILL.md — clarifies that a null os_autoclean recommendation on Linux means only that a tmpfiles.d directory exists, not that a rule covers the temp zone; this is a correction to operator-facing interpretation guidance, tightening rather than loosening what a reader should conclude before treating the zone as covered.
  • test_hygiene.py — grepped the diff for handoff: zero matches. The new assertions are all for the merged-in hint feature.

hygiene.py, destructive_guard.py, and engine_grammar.py — the files containing the handoff-apply verify/delete logic, the .git-metadata purge, and the guard/grammar wiring this PR owns — are byte-identical to bac7da7 (confirmed via empty diff output for all three). That head, and the ones before it, received thirteen prior security passes on this thread covering the fd-relative .git-metadata purge (O_NOFOLLOW symlink handling, live per-child device/mount/glob rechecks fixing the one confirmed Codex P1 — a stale pre-purge protection-glob scan, fixed in 519eab6), waiver scope requiring every nested repository to pass before any .git gets purge treatment, and the guard's additive-only handoff-apply classification — all found sound.

No instruction-surface deletions: git diff origin/main HEAD --stat -- CLAUDE.md AGENTS.md .claude/rules/ is empty for this PR's scope.

No new security findings. GitHub Actions hardening is out of scope for this lane; this PR doesn't touch .github/workflows.

I did not independently run bash hygiene.test.sh, scripts/run-ruff.sh, or markdownlint-cli2 — the PR body's reported results for those remain author-claimed, not verified by me.

@claude

claude Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 2m 22s —— View job


Code review complete

  • Gather context (PR diff vs origin/main, changed files, prior review history on this thread)
  • Invoke /review:code-review and follow its instructions exactly
  • Determine what changed since the last reviewed head (bac7da7 → b76ae7b)
  • Review new/changed logic
  • Post findings

Skip gate: this head already has a successful review from this lane that still applies.

I confirmed this PR's actual contribution against origin/main is unchanged: git diff origin/main HEAD --stat -- plugins/disk-hygiene/ still shows the same 11 files with the same +1068/-64 totals reported in the PR body.

b76ae7b is a merge of origin/main into this branch (note: this checkout is shallow, so git log <range> under-reports commits pulled in by a merge — a direct tree diff is the reliable check here). I diffed the previously fully-reviewed head (bac7da7, code-review pass at 2026-09-30T16:59:08Z) against the current head directly:

git diff bac7da7 HEAD --stat -- plugins/disk-hygiene/

Four files differ, and every change is unrelated content pulled in from origin/main (#5514, "hint Windows device-name files on Linux and explain null os_autoclean"), not authored on this branch:

  • baseline-policy.json (+108) — new windows-device-name-* hint rows (nul, con, prn, aux, com1-com9, lpt1-lpt9, with/without extension), Linux-only, files only, confidence ceiling low.
  • SKILL.md (+4/-2) — clarifies that on Linux a null os_autoclean recommendation means only that a tmpfiles.d directory exists, not that a rule there covers the temp zone.
  • CHANGELOG.md (+7) — the 0.34.2 entry for the same fix.
  • test_hygiene.py (+22) — test_windows_device_names_are_hinted_on_linux_only, exercising the new hint rows and their OS/entry-type gating.

None of this touches handoff-apply, the .git-metadata purge, destructive_guard.py, or engine_grammar.py. I confirmed those files are byte-identical to bac7da7 via direct diff (empty output):

git diff bac7da7 HEAD -- plugins/disk-hygiene/skills/clean/scripts/hygiene.py plugins/disk-hygiene/skills/clean/scripts/destructive_guard.py plugins/disk-hygiene/lib/engine_grammar.py

That head, and the ones before it, received thirteen prior review passes on this thread (code-review and security-review alternating) that already covered the handoff_apply/purge_directory_contents/opaque_contents_blocker logic in full: the fd-relative .git-metadata purge, O_NOFOLLOW symlink handling, live per-child device/mount/glob rechecks (fixing the one confirmed Codex P1, a stale pre-purge protection-glob scan, in 519eab6), waiver scope requiring every nested repository to pass before any .git gets purge treatment, and the guard's additive-only handoff-apply classification — with no blocking findings.

No instruction-surface deletions: git diff origin/main HEAD --stat -- CLAUDE.md AGENTS.md .claude/rules/ is empty for this PR's scope.

No new findings for this PR's scope. I did not independently run bash hygiene.test.sh, scripts/run-ruff.sh, or markdownlint-cli2 — the PR body's reported results for those remain author-claimed, not verified by me.

Keep disk-hygiene 0.35.0 above main's 0.34.3 with this PR's entry on top.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@claude

claude Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 57s —— View job


Code review complete

  • Gather context (PR diff vs origin/main, changed files, prior review history on this thread)
  • Invoke /review:code-review and follow its instructions exactly
  • Determine what changed since the last reviewed head (b76ae7b → 4d014da)
  • Review new/changed logic
  • Post findings

Skip gate: this head already has a successful review from this lane that still applies.

I confirmed this PR's actual contribution against origin/main is unchanged: git diff origin/main HEAD --stat -- plugins/disk-hygiene/ still shows the same 11 files with the same +1068/-64 totals reported in the PR body (matches the <changed_files> list for this event).

4d014da is a merge of origin/main into this branch. I diffed the previously fully-reviewed head (b76ae7b, code-review pass at 2026-09-30T17:16:58Z) against the current head:

git diff b76ae7b HEAD --stat -- plugins/disk-hygiene/

Only three files differ, and every change is unrelated content pulled in from origin/main (issue #5520, "clean docs match the engine": the engine, not the agent, creates the run directory, and the fan-out worker brief needs literal placeholder values since a worker can't expand ${...} tokens), not authored on this branch:

  • plugins/disk-hygiene/skills/clean/SKILL.md — rewords step 1 so the agent chooses the run-directory path and the engine creates it (it already creates --output's parent), instead of telling the agent to create the directory itself. No behavior change, no touch to handoff-apply.
  • plugins/disk-hygiene/skills/clean/reference/fan-out-worker-brief.md — adds a "fill the placeholders" section for the parent (workers can't expand ${...} tokens) and makes --project-dir explicitly optional. I checked the forbidden-command list a worker must not run: handoff-verify, handoff-apply, apply, preview, catalog, rm, del, moves, compound shells — unchanged, still present, still forbids handoff-apply. This is a clarity fix, not a guardrail change.
  • plugins/disk-hygiene/CHANGELOG.md — the new 0.34.3 entry for the same doc fix.

hygiene.py, destructive_guard.py, engine_grammar.py, and test_hygiene.py — the files containing the handoff-apply/purge logic this PR owns — are byte-identical to b76ae7b (confirmed via empty diff output across all four). That head, and the ones before it, received fourteen prior review passes on this thread (code-review and security-review alternating) that already covered the handoff_apply/purge_directory_contents/opaque_contents_blocker logic in full: the fd-relative .git-metadata purge, O_NOFOLLOW symlink handling, live per-child device/mount/glob rechecks (fixing the one confirmed Codex P1 — a stale pre-purge protection-glob scan, fixed in 519eab6), waiver scope requiring every nested repository to pass before any .git gets purge treatment, and the guard's additive-only handoff-apply classification — with no blocking findings.

No instruction-surface deletions: git diff origin/main HEAD --stat -- CLAUDE.md AGENTS.md .claude/rules/ is empty for this PR's scope.

No new findings for this PR's scope. I did not independently run bash hygiene.test.sh, scripts/run-ruff.sh, or markdownlint-cli2 — the PR body's reported results for those remain author-claimed, not verified by me.

@claude

claude Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 40s —— View job


Security review complete

  • Gather context (PR diff vs origin/main, changed files, prior review history on this thread)
  • Invoke /review:security-review and follow its instructions exactly
  • Determine what changed since the last fully-reviewed head (b76ae7b → 4d014da)
  • Re-check any new security-relevant surface
  • Check instruction-surface deletions (CLAUDE.md/AGENTS.md/rules/skills)
  • Post final findings

Skip gate: this head already has a successful security review that still applies.

This PR's actual scope against origin/main is unchanged: git diff origin/main HEAD --stat -- plugins/disk-hygiene/ still shows exactly the same 11 files with the same +1068/-64 totals reported in the PR body.

I diffed the previously fully-reviewed head (b76ae7b, security pass at 2026-09-30T17:16:57Z) against the current head 4d014da:

git diff b76ae7b HEAD --stat -- plugins/disk-hygiene/

Only three files differ, all pure documentation (fixing #5520, "SKILL.md and fan-out-worker-brief.md docs match the engine"), pulled in from origin/main by this merge, not code this PR's handoff-apply feature owns:

  • CHANGELOG.md — new 0.34.3 entry for the docs fix.
  • skills/clean/SKILL.md — rewords step 1 to say the engine creates the run directory (it creates the parent of --output) rather than telling the agent to create it beforehand. Descriptive correction, not a behavior or guardrail change.
  • skills/clean/reference/fan-out-worker-brief.md — adds an explicit "parent must fill placeholders with literal absolute values before spawning" section (a worker cannot expand ${...} tokens; the guard rejects shell expansion), and makes --project-dir explicitly optional. This tightens the worker-brief instructions rather than loosening them: the existing prohibition list (no apply/preview/handoff-verify/handoff-apply/catalog/rm/del/moves/compound shells) is unchanged and still present verbatim.

hygiene.py, destructive_guard.py, engine_grammar.py, and test_hygiene.py — the files containing the handoff-apply verify/delete logic, the .git-metadata purge, and the guard/grammar wiring this PR owns — are byte-identical to b76ae7b (confirmed via empty diff output for all four). That head, and the ones before it, received fourteen prior security passes on this thread covering the fd-relative .git-metadata purge (O_NOFOLLOW symlink handling, live per-child device/mount/glob rechecks fixing the one confirmed Codex P1 — a stale pre-purge protection-glob scan, fixed in 519eab6), waiver scope requiring every nested repository to pass before any .git gets purge treatment, and the guard's additive-only handoff-apply classification — all found sound.

No instruction-surface deletions: git diff origin/main HEAD --stat -- CLAUDE.md AGENTS.md .claude/rules/ is empty for this PR's scope, and nothing in the three changed files removes or softens an existing guardrail.

No new security findings. GitHub Actions hardening is out of scope for this lane; this PR doesn't touch .github/workflows.

I did not independently run bash hygiene.test.sh, scripts/run-ruff.sh, or markdownlint-cli2 — the PR body's reported results for those remain author-claimed, not verified by me.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@claude

claude Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 1m 22s —— View job


Code review complete

  • Gather context (PR diff vs origin/main, changed files, prior review history on this thread)
  • Invoke /review:code-review and follow its instructions exactly
  • Determine what changed since the last reviewed head (4d014da → c7fa7a0)
  • Review new/changed logic
  • Post findings

Skip gate: this head already has a successful review from this lane that still applies.

I confirmed this PR's actual contribution against origin/main is unchanged: git diff origin/main HEAD --stat -- plugins/disk-hygiene/ still shows exactly the same 11 files with the same +1068/-64 totals reported in the PR body (matches the <changed_files> list for this event).

c7fa7a0 is a merge of origin/main into this branch. I diffed the previously fully-reviewed head (4d014da, code-review pass at 2026-09-30T17:34:31Z) against the current head:

git diff 4d014da c7fa7a0 --stat -- plugins/disk-hygiene/

Eight files differ, and every change belongs to an unrelated, already-merged fix (#5587, "gate --sizes-only behind the large-scan confirmation and keep no per-path entries," closing #4009), not authored on this branch:

  • hygiene.py — refactors children_rollup into a streaming accumulate_child_rollup/child_rollup_rows pair and touches scan_tree (L2384-2664) so a sizes-only walk folds each path straight into the rollup instead of retaining one entry per path, plus a small main() change for the same gating (L5339, L5413). I grepped this whole diff for handoff, purge_directory, and opaque_contents: zero matches. handoff_apply, purge_directory_contents, opaque_contents_blocker, and evidence_adjusted_protections (this PR's own code, ~L4340-4801) are untouched.
  • destructive_guard.py / engine_grammar.py — zero diff, confirmed by empty git diff output. The handoff-apply guard classification and grammar spec this PR owns are completely unchanged.
  • SKILL.md, reference/safety-model.md, README.md, reference/fan-out-worker-brief.md, reference/scan-flags.md — all reword the --sizes-only large-scan-gate description (it now asks the same large-scan question as an unbounded walk, keeps no per-path entries). I read the full text of each hunk; none mentions handoff-apply, the .git-metadata purge, or the Linux checkout-acknowledgement route.
  • CHANGELOG.md — the new 0.34.4 entry for the same --sizes-only fix.
  • test_hygiene.py (+126) — grepped the diff for andoff: zero matches; the new assertions are all for the --sizes-only gating change.

No instruction-surface deletions: git diff origin/main HEAD --stat -- CLAUDE.md AGENTS.md .claude/rules/ is empty for this PR's scope.

No new findings for this PR's scope. The handoff_apply/purge_directory_contents/opaque_contents_blocker logic, the guard's additive-only handoff-apply classification, and the grammar spec were already reviewed in full across fifteen prior passes on this thread (code-review and security-review alternating), including the one confirmed Codex P1 (a stale pre-purge protection-glob scan), fixed in 519eab6 and independently verified. Nothing since has touched that code path.

I did not independently run bash hygiene.test.sh, scripts/run-ruff.sh, or markdownlint-cli2 — the PR body's reported results for those remain author-claimed, not verified by me.

@claude

claude Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 59s —— View job


Security review complete

  • Gather context (PR diff vs origin/main, changed files, prior review history on this thread)
  • Invoke /review:security-review and follow its instructions exactly
  • Determine what changed since the last fully-reviewed head (4d014da → c7fa7a0)
  • Re-check any new security-relevant surface
  • Check instruction-surface deletions (CLAUDE.md/AGENTS.md/rules/skills)
  • Post final findings

Skip gate: this head already has a successful security review that still applies.

This PR's actual scope against origin/main is unchanged: git diff origin/main HEAD --stat -- plugins/disk-hygiene/ still shows exactly the same 11 files with the same +1068/-64 totals reported in the PR body.

I diffed the previously fully-reviewed head (4d014da, security pass at 2026-09-30T17:34:32Z) against the current head c7fa7a0:

git diff 4d014da HEAD --stat -- plugins/disk-hygiene/

Eight files differ, and every change is content merged in from origin/main (#5587, "gate --sizes-only behind the large-scan confirmation and keep no per-path entries"), not authored on this branch, and none of it touches handoff-apply:

  • hygiene.py (+153/-…) — every hunk sits inside children_rollup, scan_tree, and main()'s large-scan confirmation gate (removing a sizes_only carve-out so --sizes-only now goes through the same --confirmed-large-scan gate as any other scan, and keeps no per-path entries). I grepped the diff for the functions this PR actually owns — handoff, purge_directory_contents, opaque_contents_blocker, evidence_adjusted_protections — and got zero matches.
  • destructive_guard.py and engine_grammar.py — byte-identical to 4d014da (empty diff output). The handoff-apply grammar spec and the guard's ask/kill-switch classification for it are untouched.
  • test_hygiene.py (+126) — new tests are test_sizes_only_plain_target_goes_through_the_large_scan_gate, test_sizes_only_root_children_goes_through_the_large_scan_gate, test_sizes_only_totals_match_an_ordinary_scan_without_retaining_entries, test_sizes_only_keeps_a_bounded_sample_of_empty_directories — all for the merged-in --sizes-only fix; no HandoffApplyTests/HandoffApplyPlatformTests change.
  • README.md, SKILL.md, fan-out-worker-brief.md, safety-model.md, scan-flags.md, CHANGELOG.md — the only lines matching "handoff" are pre-existing, unchanged context lines (a table row referencing "manual handoff (§6)" and unrelated report-only-handoff prose about managed state); the actual diffs describe --sizes-only's gating and snapshot-refusal behavior, not the Linux handoff-apply route or the .git-metadata purge.

hygiene.py's handoff_apply, purge_directory_contents, opaque_contents_blocker, and evidence_adjusted_protections — the functions this PR owns — remain byte-identical to the last fully-reviewed head. That head, and the ones before it, received sixteen prior security passes on this thread covering the fd-relative .git-metadata purge (O_NOFOLLOW symlink handling, live per-child device/mount/glob rechecks fixing the one confirmed Codex P1 — a stale pre-purge protection-glob scan, fixed in 519eab6), waiver scope requiring every nested repository to pass before any .git gets purge treatment, and the guard's additive-only handoff-apply classification — all found sound.

No instruction-surface deletions: git diff origin/main HEAD --stat -- CLAUDE.md AGENTS.md .claude/rules/ is empty for this PR's scope.

No new security findings. GitHub Actions hardening is out of scope for this lane; this PR doesn't touch .github/workflows.

I did not independently run bash hygiene.test.sh, scripts/run-ruff.sh, or markdownlint-cli2 — the PR body's reported results for those remain author-claimed, not verified by me.

@kyle-sexton
kyle-sexton merged commit 2e0be76 into main Sep 30, 2026
27 checks passed
@kyle-sexton
kyle-sexton deleted the feat/5178-linux-accept-unpublished-route branch September 30, 2026 18:01
kyle-sexton added a commit that referenced this pull request Sep 30, 2026
Main took disk-hygiene 0.35.0 (handoff-apply, #5541), so the inventory release
moves to 0.36.0 above it. The destructive guard's read-only set now lives in
main's frozenset beside the mutating set, and inventory joins the read-only
side; the tests that named the old tuple or pinned apply as the last
subcommand follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kyle-sexton added a commit that referenced this pull request Sep 30, 2026
Main's handoff_apply (#5541) also reaches anchored_remove, and its
purge_directory_contents and write_text_atomic delete too, so the guard's
apply-lane set and the one-caller assertions no longer described the engine.

Deleting is now allowed only in apply_plan, handoff_apply, anchored_remove,
purge_directory_contents and write_text_atomic (its own temporary file).
A registry command or tool may appear only in apply_plan: handoff_apply
takes no plan, so it has no owner claim, and the test pins that signature.
Each lane has main as its only caller, anchored_remove has exactly those
two, and only the handoff lane asks it to empty Git metadata. The engine
is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kyle-sexton added a commit that referenced this pull request Sep 30, 2026
…engine-gate denial (#5582)

Refs #5519

## Summary

The Bash engine-gate denial in `destructive_guard.py` gave no hint which
token failed the exact-engine grammar, what the flag-order rule is, or
which mention forms are gated. The denial now says all three. No
behavior change: the guard matches and denies exactly the same commands.

This PR uses `Refs`, not `Closes`; whether it closes #5519 is left to
the owner.

## Fix

- `_engine_mismatch_reason` names what the classifier refuses (parse,
word count, interpreter, engine script, subcommand, `--data-root`,
per-subcommand grammar via `engine_grammar.explain_mismatch`). It runs
only on the deny path.
- An engine operand of a command that is not the hook's Python (`grep
foo "<engine path>"`, `cat "<engine path>"`) is named first, as an
absolute engine path or as a relative word that resolves to the engine
from the current directory. Before, the reason blamed the command word
(`grep`) as "not this hook's Python".
- A word the gate reads as an engine call (a quoted payload holding an
interpreter and the engine filename, such as `gh issue list --search
"python3 <engine>"`) is named as the gated word. Before, the reason
named `gh` and asked for the hook's Python. The gate and the reason
share one helper, `_reads_as_engine_payload`, so they cannot disagree on
which word gated.
- An unparsable command names the first operator class present (pipe,
redirect, `;`, `&`, substitution, glob, newline, `!`/`#`, backslash,
quote) instead of listing all of them. A test pins the label table to
the characters the literal parser rejects.
- `_engine_flag_order_rule` states the flag-order rule from the declared
subcommand specs, and `_ENGINE_GATE_SCOPE` states the gated mention
forms and the read-only forms in the owner's words, with the condition
the owner chose (option 2, PR comment 2026-09-30T19:04Z): a relative
path or bare name that resolves to the installed engine from the current
directory is still gated.
- Relaxing the guard (option a) is deferred by the owner's decision to a
separate, security-reviewed change.
- `disk-hygiene` 0.41.1 with a CHANGELOG entry above 0.41.0. No doc
quotes the old denial text.

## Owner decision applied

The Codex P2 thread found that the advertised read-only forms are denied
when the word resolves to the installed engine (the literal branch of
`_engine_gate_relevant` gates on file identity). The owner took option
2: keep the forms and add the condition.
`test_engine_gate_gates_a_relative_word_that_resolves_to_the_engine`
pins both the gated shapes and that their denial states the condition.

`_engine_gate_relevant` result per working directory:

| Working directory | Command | Gated |
|---|---|---|
| plugin root or repo root | `grep foo <relative path to the engine>` |
yes |
| plugin root or repo root | `git grep foo -- <relative path to the
engine>` | yes |
| repo root | `rg foo <bare engine name>` | no |
| the engine's directory | `rg foo <bare engine name>` | yes |
| the engine's directory | `git grep foo -- <bare engine name>` | yes |
| any directory tried | `git show <rev>:<path to the engine>` | no |
| an unrelated directory | `grep foo <relative path to the engine>` | no
|

## Verification

- `test_hygiene` (run as CI does, from the scripts directory): 674 tests
OK (1 skipped) on the merged head, including tests for the denial text,
agreement between the explainer and the classifier over every declared
subcommand (`handoff-apply` included), the advertised read-only forms
and their resolving-word condition, the engine operand reason, the
payload-word reason, and the operator reason. The payload-word test also
asserts `_engine_gate_relevant` still gates those commands and still
defers a plain mention.
- The rest of the disk-hygiene Python suites (scripts, lib, setup): OK.
- `scripts/run-ruff.sh check` on the changed files: all checks passed.
- `scripts/check-changelog-parity.sh --check`, `--check-order`, and
`scripts/validate-plugins.sh`: pass.

## Related

- Message-only precedent: #3348.
- #5214 is the parent issue. #4218 and #3527 are the same-fault items
for the deferred relaxation.
- Version: 0.41.1, above main's 0.41.0. This branch merged main, which
carries #5541 (`handoff-apply`), #5526, #5590 and #5628 for
disk-hygiene. The final deny in `_decide` is now main's single
`not-exact-engine-command` call with `command=command` added, and the
explainer reads the declared subcommand specs, so it covers
`handoff-apply`'s required flags.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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.

disk-hygiene: accept_unpublished has no deletion route on Linux (handoff-verify is Windows/macOS only)

1 participant