feat(disk-hygiene): add model-invocable read-only audit skill - #5590
Conversation
/disk-hygiene:audit runs the engine's scan subcommand only, after the argument-free kill-switch probe, and reports the snapshot. Removal stays with /disk-hygiene:clean, which a person invokes. Safety rests on the plugin-level engine-gate; the skill carries no hooks of its own. Refs #5516 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The probe reports hook_python as the interpreter it ran under and the engine-gate does not fire on the probe path, so a bare-python run names nothing. Say what the probe reports, let the scan's own denial supply the guard's interpreter, drop the null-data-root denial that never exists, and drop the unverified claim about the gate reaching subagents. Refs #5516 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… 0.30.0 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e-audit-skill Rebump disk-hygiene to 0.31.0 above main's 0.30.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
PR body contract — issue linkage This PR body conforms to the issue-linkage contract. Nothing to do. |
…kill with the delegated-worker contract Refs #5516 Register disk-hygiene as the 17th owner of the `audit` leaf name and extend the rationale, so check-skill-leaf-names.sh --check passes. In the audit skill, replace the literal ${CLAUDE_PROJECT_DIR} in the scan template with a <project-dir> placeholder filled with a literal absolute path, tell the parent to fill every token and placeholder in the worker brief from the probe values before spawning, and give the inline --plugin-dir data_root null gotcha its claim/basis/as-of/recheck record. Correct the null-data-root eval, which expected a guard denial that never occurs because no engine call is submitted, and link the 0.31.0 changelog entry to #5516. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e-audit-skill Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e-audit-skill Main claimed disk-hygiene 0.31.0 for the investigated-entry catalog, so the audit skill renumbers to 0.32.0 above it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… tokens that break Refs #5516 Name ${CLAUDE_PROJECT_DIR} and ${CLAUDE_PLUGIN_DATA} in the scan-template warning instead of every ${...} token, since the skill's own commands use ${CLAUDE_PLUGIN_ROOT}, and word the leaf-name rationale as joining on its own grounds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e-audit-skill Main claimed disk-hygiene 0.31.1 for the entry-cap error fix, so the audit skill stays at 0.32.0 above it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e-audit-skill Main claimed disk-hygiene 0.32.0 for virtual-disk image protection, so the audit skill renumbers to 0.33.0 above it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e-audit-skill Main added disk-hygiene 0.32.1; the audit skill stays at 0.33.0 above it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e-audit-skill Main claimed disk-hygiene 0.33.0 for the partial-totals labeling, so the audit skill renumbers to 0.34.0 above it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e-audit-skill Main added disk-hygiene 0.33.1; the audit skill stays at 0.34.0 above it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e-audit-skill Renumber disk-hygiene to 0.35.0: main now carries 0.34.0 (#5528) and 0.34.1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ituted tokens Refs #5516 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Claude finished @kyle-sexton's task in 4m 49s —— View job Security review —
|
|
Claude finished @kyle-sexton's task in 6m 9s —— View job Review completeRan What I checked
FindingsNone that clear this lane's high-signal bar (nothing a linter/static check would catch, and nothing new beyond what the PR body already discloses). The two open gaps called out in the "Hold" section — lane c ( No inline comments posted; this PR's |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 75bff7ac10
ℹ️ 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".
|
Lane c result (owner-run, 2026-09-30): the destructive guard's
A first attempt without Not probed: an interactive session in Transcript: |
|
Owner decision (2026-09-30): release the hold. Condition 4 on #5516 is met by the three probe lanes (headless default denied, |
Resolve the disk-hygiene version and changelog (0.35.0 above 0.34.4) and address the Codex review on the audit skill: add the verification record to the permission gotcha, stop probing under bare python, and state that a scan does not assess locked, needs-elevation, or unverified entries. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 2m 59s —— View job Code review complete
Ran Findings1 finding, posted inline on The bootstrap fallback ("submit the probe once with bare Not re-reportedThe three Codex findings from the earlier review round (verification record on the permission gotcha, resolving the guard's interpreter before probing, and not claiming unassessed live-state categories) all appear to already be addressed in the current No other high-signal correctness/maintainability issues found in the remaining changed files ( Note: this PR's |
|
Claude finished @kyle-sexton's task in 1m 50s —— View job Security review —
|
…5613) Refs #5609 ## Summary Adds the four-part verification record (claim, basis, as-of, recheck) to `safety-model.md` for how the PreToolUse `ask` on `hygiene.py apply --execute` behaves across permission modes. It states the three modes probed, the modes not probed, and what the official docs and upstream issues say. ## Fix Only AC3 of #5609 is met. AC1 (interactive `bypassPermissions` and a Windows host probe) needs operator-only sessions and is not faked here; AC2 (a gate decision) depends on AC1 and stays with the owner. The issue stays open. The plugin is bumped to 0.35.1 with a CHANGELOG entry. ## Verification - `scripts/check-changelog-parity.sh --check`, `--check-order`, `--check-bump origin/main`: pass. - `scripts/validate-plugins.sh`: all manifests and the catalog validated. - Docs-only change; no plugin tests apply. ## Related - Issue #5609 - Probe evidence: PR #5590 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…ootstrap Merge origin/main, take the plugin to 0.36.0 above main's 0.35.0, and restore the audit skill's scan-denial path: the audit carries no skill-level belt, so the probe is not denied. An older hook_python from a bare-python probe no longer stops the audit; the scan's admission settles the guard's interpreter. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keep the disk-hygiene version at 0.36.0 above main's 0.35.1 and put this PR's changelog entry on top. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 1m 38s —— View job Security review —
|
|
Claude finished @kyle-sexton's task in 2m 37s —— View job Code review complete
Ran What changed since the last review roundThe last code-review pass on this lane reviewed
FindingsNone. Re-verified the previously-flagged bootstrap logic line-by-line against No inline comments posted this round — no new high-signal correctness/maintainability issues found beyond what earlier rounds already covered and this PR already fixed. Note: this PR's hold has been released by the owner and merge order is documented in the PR body ( |
Keep the disk-hygiene version at 0.36.0 above main's 0.35.2 and put this PR's changelog entry on top. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 59s —— View job Code review complete
Ran Skip gate: appliesThis lane's skip gate stops deep review when "this head already has a successful review from this lane that still applies." That's the case here:
No new correctness/maintainability surface was introduced by the merge commit itself ( Note: this PR carries a |
|
Claude finished @kyle-sexton's task in 58s —— View job Security review —
|
…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>
…ogic with claude-ops (#5632) Closes #5221 ## Summary Items 1, 2, 4, 5 and 7 of #5221 landed in #5585. This PR completes item 3 (reuse the unreferenced-cache-version logic from the claude-ops install-state audit) and item 6 (coordination with the related deep-inventory work). `disk-hygiene`'s deep inventory carried its own plugin-cache-version logic, a divergent copy of the claude-ops `audit-install-state` rule: it read `installed_plugins.json` without the guarded read the audit uses. ## Fix - One module, `lib/plugin_cache_versions.py`, is carried byte-identical in `plugins/claude-ops/lib/` and `plugins/disk-hygiene/lib/`. `install_state.py` and `deep_inventory.py` both call it, so both apply one rule for which cache versions are unreferenced. - The module is registered in `scripts/cross-plugin-source-registry.txt`, so `scripts/check-cross-plugin-source-drift.sh` fails if the copies diverge. - `claude-ops` 0.77.3 and `disk-hygiene` 0.40.1, each with a CHANGELOG entry. - Item 6: no second listing to align. The successor of closed #5214, `/disk-hygiene:audit` (#5590), reads the scan `children_rollup`. The shared listing schema for the managed-state lane (#4006) lives in `plugins/disk-hygiene/skills/clean/scripts/deep_inventory.py` (`ROW_COLUMNS`). #4006 is not folded in and its scope is unchanged. ## Verification - `bash scripts/check-changelog-parity.sh --check`: pass - `bash scripts/check-changelog-parity.sh --check-order`: pass (102 changelogs) - `bash scripts/check-changelog-parity.sh --check-bump origin/main`: pass - `bash scripts/validate-plugins.sh`: all manifests and the catalog validated - `bash scripts/check-changed-skills.sh origin/main`: 2 skills checked, 0 failed - `bash scripts/check-cross-plugin-source-drift.sh`: exit 0; `lib/plugin_cache_versions.py` IDENTICAL [registered] - `python3 -m unittest` on `test_deep_inventory.py` (52 tests) and `test_install_state.py` (91 tests): OK ## Related - #5585: landed items 1, 2, 4, 5, 7 of #5221 - #5420 - #5590: successor of closed #5214; it reads scan `children_rollup`, so there is no second listing to align - #4006: open; the shared listing schema is `ROW_COLUMNS` in `plugins/disk-hygiene/skills/clean/scripts/deep_inventory.py`, for the managed-state lane to emit. Not folded in here. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Refs: #5516
Hold: do not merge
The
do-not-mergelabel is applied. Owner condition 4 on #5516 says to probe the guardaskonapply --executeunderbypassPermissionsbefore the skill ships. Lane c was not run: the auto-mode classifier denied the launch script and the denial was not worked around. Auto mode, this machine's default, was not probed either. Release the hold only after the owner runs lane c (claude -p --permission-mode bypassPermissions --tools Bash, same scratch target and prompt as lanes a and b) or accepts the gap in a comment here or on #5516. If lane c fails open, condition 4 says fall back to A2 for that lane.Second owner question: the argument-free kill-switch probe is not matched by the engine-gate filter, so a delegated audit in default mode stops at step 1 without a user allow rule for the probe. Accept that, or add the allow rule.
Merge order: #5571 first, then this PR (see Related).
Summary
Adds
/disk-hygiene:audit, a model-invocable, read-only skill that runs the kill-switch probe and one enginescanand reports the snapshot. It runs noprevieworapply; removal stays a separate/disk-hygiene:cleanrun a person invokes.cleanis unchanged. Ships together with #5571 (#5520).Fix
skills/audit/SKILL.mdandevals/evals.json(three delegated-audit cases and one negative case where "clean up my disk / delete these" must route to/disk-hygiene:clean).<project-dir>placeholder (a literal absolute path, optional) in place of${CLAUDE_PROJECT_DIR}, and the skill tells the parent to replace every${...}token and<placeholder>in the fan-out worker brief with the probe'shook_pythonanddata_rootbefore spawning, since a worker cannot expand tokens.--plugin-dirdata_root: nullgotcha carries a claim, basis, as-of and recheck record. The null-data_rooteval no longer expects a guard denial to relay: no engine call is submitted in that case.auditis registered inscripts/skill-leaf-name-registry.txtwith disk-hygiene as its 17th owner and a rationale paragraph.Verification
Probe results (claude 2.1.285, worktree plugin loaded with
--plugin-dir,permission-mode default, fresh scratch target):apply --executedenied by the guard (exact-engine-apply, ask);staging.tmpsurvived.--bgdefault: parked at a permission prompt on the apply;staging.tmpsurvived.bypassPermissions: not probed. Auto mode (this machine's default) not probed.Bash(*hygiene.py*)), so a default-mode model-invoked session needs a user approval or allow rule to run it. Under--plugin-dirthe probe reportsdata_rootnull.Checks run locally on a9f61a8 (after merging origin/main and renumbering 0.34.0 to 0.35.0), all exit 0:
check-skill-leaf-names.sh --checkand its.test.sh;check-adr-numbers.sh --check;check-changelog-parity.sh --check,--check-order,--check-bump origin/main;validate-plugins.sh;check-changed-skills.sh origin/main;check-skill-count-claims.sh;check-spoke-plugin-root.sh --check;check-docs-naming.sh --check;check-orphaned-fixtures.sh --check;generate-cheatsheet.mjs --check;check-evals-quality.shandjson.toolon the audit evals;hygiene.test.sh(592 tests, 1 skipped) andkill_switch_probe.test.sh. The earlier "all pass" was wrong: on 4c4faf7 CIlint-2failedskill-leaf-names(fixed by the registry entry) andadr-numbers(a duplicate 0042 on main, fixed by main's renumber, which this branch now includes).The A2 fallback and the final call on closing #5516 stay with the owner, so this PR uses
Refs, notCloses.Related
#5516; #5520 / #5571 (ships together).
Merge order: #5571 lands first. This PR links its worker brief, which still shows
${CLAUDE_PLUGIN_DATA}and${CLAUDE_PROJECT_DIR}tokens until #5571 lands. #5571 is stale: its base predates main's 0.29.2 through 0.31.0 changelog entries, so it needs a rebase and a renumber above main's current version before it can merge. disk-hygiene versions on main keep moving, so before this PR merges, merge main and re-runscripts/check-changelog-parity.sh --check-bump origin/main, renumbering above main's then-current version. The repo squash-merges only, so the branch'sbump to 0.30.0commit subject does not reach main.🤖 Generated with Claude Code