fix(disk-hygiene): guard resolves authorized data root from hook argv#742
Conversation
The clean skill declares destructive_guard.py as a skill-frontmatter PreToolUse
hook. The guard read its authorized data root only from CLAUDE_PLUGIN_DATA, but
the runtime does not inject that variable into a skill-frontmatter hook's process
environment, so --data-root never validated and the scan/preview/apply engine
lane failed closed on every guarded invocation, on all platforms.
Inline placeholder substitution does resolve in hook arguments (proven by
${CLAUDE_PLUGIN_ROOT} already resolving in this same hook). The frontmatter hook
now passes --authorized-data-root ${CLAUDE_PLUGIN_DATA}, and the guard resolves
its authority from that argv, honoring CLAUDE_PLUGIN_DATA only as a fallback. The
security property is unchanged: authority comes from the guard's own
harness-substituted argv, a channel distinct from the model-supplied --data-root,
which is validated against it and can never become its own authority.
Corrects the two doc sentences that asserted the false env-injection mechanism
and locks the config<->code flag seam with a regression test.
Closes #376
Co-Authored-By: Claude <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 3m 9s —— View job PR Review
|
Add hook-argv integration tests mirroring the scan-shape test so the new --authorized-data-root argv path is proven end-to-end for the preview and apply command shapes, not only scan. Each verifies the authorized root supplied solely via hook argv (CLAUDE_PLUGIN_DATA absent) yields the shape's expected allow/ask verdict, while a mismatched root denies. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 2m 12s —— View job PR Review
SummaryCommit A few observations on the new tests: Apply correctly expects
Status of prior findings
Findings 2–4 were all low/style in the prior review and are still accurate. No regression introduced by this commit. The one residual item worth noting:
VerdictApprove. The primary medium finding from the prior review is fully resolved. The implementation is correct, the security property is intact, and the test structure matches the real hook wiring. Remaining items are all low/style and do not block merge. |
|
Claude finished @kyle-sexton's task in 4m 35s —— View job Security Review
OverviewThe PR changes one security-critical function: how The core trust model claim is: the guard's own process argv is a model-unforgeable channel, because it is set by the harness from the skill-frontmatter This claim is correct. The two channels are genuinely disjoint:
There is no code path by which a value in FindingsSUGGESTION — PowerShell engine-detection relies on filename string, not script identityConfidence: low if "hygiene.py" in command.casefold():The PowerShell guard detects engine invocations by checking whether the string Mitigations that limit this in practice: (a) the model cannot create a symlink without a Bash command, which goes through the fail-closed exact-match lane; (b) the Bash lane is the only authoritative engine lane — PowerShell engine invocations cannot produce a successful No action required before merge; tracking as a future hardening opportunity. Pre-existing issue (not introduced by this PR) — kill-switch always reads as enabled in hook contextenabled = os.environ.get("CLAUDE_PLUGIN_OPTION_DISK_HYGIENE_ENABLED", "true").lower() != "false"
This is acknowledged and tracked separately. It pre-dates this PR; the diff here touches What was checked
Verdict: no blocking security issues. The argv-authority channel is architecturally correct and model-unforgeable as claimed. The one SUGGESTION (PowerShell filename-string check) is pre-existing defense-in-depth rather than a new vulnerability. |
|
Post-green review pass complete (rule 8). Security review (this repo's new No threads to resolve, CI fully green (17/17). Flipping to ready for review; handing off to the babysit lane for merge. |
|
Claude finished @kyle-sexton's task in 2m 50s —— View job PR Review
SummaryThe fix is architecturally correct and all three previous reviews' findings are in the expected state. The two commits together are mergeable. What the fix does: Trust model: The two channels are genuinely disjoint. Detailed findings
Scan/preview/apply shape coverage — complete. All three engine shapes are now exercised via the hook-argv path (no Seam-lock test — well-designed.
Status of all prior findings
Finding #3 note: VerdictApprove. Fix is correct, security property is intact, test coverage for all engine shapes via the hook-argv path is present, and the seam-lock test is a strong regression guard. All prior findings are in the expected state — the one medium item (#1) is fully resolved; the remaining items are all low/style and do not block merge. |
|
Claude finished @kyle-sexton's task in 3m 37s —— View job Security Review
OverviewThis PR changes one security-critical mechanism: how The core trust model claim is: the guard's This claim is correct. The two channels are genuinely disjoint. No code path in the guard causes FindingsNo CRITICAL or IMPORTANT security issues were found in this diff. SUGGESTION —
|
| Area | Result |
|---|---|
Authority channel separation (sys.argv vs sys.stdin) |
Sound — no cross-contamination path |
${ guard for unsubstituted placeholder |
Correct; if from_argv also catches empty string |
_data_root_key canonicalization (expanduser + resolve) |
Both sides resolved equally; equality check prevents prefix bypass |
_literal_shell_words metacharacter rejection |
Blocks ~, $, {, ;, ` |
_is_current_python samefile + normcase identity check |
Correct; symlink aliases and bare python rejected |
_script_path_key resolve(strict=True) |
Correct; non-existent path returns None and fails closed |
Approval-token regex [0-9a-f]{24} |
Unchanged; correct |
is_exact_engine_apply signature change, no callers |
Dead code concern (tracked in prior review), not a security issue |
JSON payload parsing from sys.stdin |
Standard json.load; command validated as str |
SKILL.md frontmatter args array form |
Correct two-element split: harness delivers --authorized-data-root and <value> as separate argv items |
| GitHub Actions / workflow files | Not changed in this PR |
Verdict: no blocking security issues. The argv-authority channel is architecturally correct and model-unforgeable as claimed. Both SUGGESTIONS are low-impact style points that do not affect the security property. The one pre-existing concern (kill switch env injection, #382) is noted but pre-dates this diff.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 53ee8a3506
ℹ️ 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".
resolve_authorized_data_root discarded any argv --authorized-data-root
value containing the substring "${", not just the exact unsubstituted
${CLAUDE_PLUGIN_DATA} placeholder. In the supported runtime the hook
environment is intentionally absent, so an install whose real data-root
path merely contains "${" lost its argv authority and failed closed on
every scan/preview/apply invocation.
Match only the exact placeholder token via a single-source-of-truth
_AUTHORIZED_DATA_ROOT_PLACEHOLDER constant. Fail-closed posture is
preserved: the model channel rejects $/{/} via
_SHELL_EXPANSION_OR_OPERATOR_CHARS and Path().expanduser().resolve()
does not expand ${}, so a ${}-bearing authority can never match a
model-supplied --data-root.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 2m 53s —— View job Security Review
OverviewThis pass covers the full PR (commits The core trust model claim is unchanged from prior reviews: the guard's Finding: No CRITICAL or IMPORTANT security issuesAnalysis of the
|
| Area | Status |
|---|---|
Kill switch (CLAUDE_PLUGIN_OPTION_DISK_HYGIENE_ENABLED) always enabled in hook context |
Pre-existing; tracked as #382 |
PowerShell filename-string detection ("hygiene.py" in command.casefold()) won't catch symlink aliases |
Pre-existing defense-in-depth gap; noted in prior security review |
is_exact_engine_apply signature changed, no callers or direct tests |
Noted in prior code review; no security consequence |
What was checked
| Area | Result |
|---|---|
sys.argv vs sys.stdin channel separation |
Sound — no cross-contamination path |
_AUTHORIZED_DATA_ROOT_PLACEHOLDER f-string correctness |
Correct — evaluates to "${CLAUDE_PLUGIN_DATA}" |
| Exact-equality vs substring check for placeholder | Correct fix — only the literal unsubstituted token is rejected |
Fail-closed with ${}-bearing authority path |
Holds — model channel rejects $/{/} via _SHELL_EXPANSION_OR_OPERATOR_CHARS |
| Fail-closed with empty-string substitution | Holds — if from_argv is False for "" |
_data_root_key expanduser on model-supplied values |
Safe — ~ rejected upstream by _literal_shell_words |
_bash_denial_guidance output |
No injection risk — result is JSON-serialized by json.dumps |
SKILL.md args array wiring |
Correct two-element split: "--authorized-data-root" and "${CLAUDE_PLUGIN_DATA}" as separate entries |
| GitHub Actions / workflow files | Not changed in this PR |
Verdict: no blocking security issues. The placeholder-check fix is correct, the fail-closed posture is preserved, and the authority channel remains model-unforgeable.
|
Claude finished @kyle-sexton's task in 2m 5s —— View job PR Review
SummaryCommit What changed
_CLAUDE_PLUGIN_DATA_ENV = "CLAUDE_PLUGIN_DATA"
_AUTHORIZED_DATA_ROOT_PLACEHOLDER = f"${{{_CLAUDE_PLUGIN_DATA_ENV}}}"Two new module-level constants make the env-var name and the exact placeholder token single sources of truth.
# before
if from_argv and "${" not in from_argv:
return from_argv
return os.environ.get("CLAUDE_PLUGIN_DATA")
# after
if from_argv and from_argv != _AUTHORIZED_DATA_ROOT_PLACEHOLDER:
return from_argv
return os.environ.get(_CLAUDE_PLUGIN_DATA_ENV)The exact-equality check is strictly more precise than the substring check. A path like Security preservation: The fail-closed posture for the model channel is unchanged. Test coverage
with mock.patch.object(
guard.sys,
"argv",
[script, "--authorized-data-root", "/data/${weird}/root"],
):
self.assertEqual(
"/data/${weird}/root",
guard.resolve_authorized_data_root(),
"only the exact placeholder is rejected; a real path that merely "
"contains ${ must be preserved as the argv authority",
)This directly exercises the Codex finding: a path with One minor note (not a blocker): The seam-lock test at L1508 still uses the string literal Status of all prior findings
VerdictApprove. The Codex finding is fully resolved. The fix is correct, the security property is intact, and the new test directly exercises the previously broken edge case. All prior medium findings are addressed; remaining items are low/style with no regression introduced by any commit in this branch. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b7e048aca0
ℹ️ 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".
…ter (#936) ## Summary The `disk-hygiene` `clean` skill's PreToolUse destructive-safety guard launched in **exec form** via a single unqualified interpreter. Per the [hooks reference](https://code.claude.com/docs/en/hooks), exec form (an `args` array is present) resolves `command` on `PATH` with **no shell**, "all matching hooks run in parallel", and any non-zero exit that is not exit-2 is "a non-blocking error … Execution continues." A hook that cannot launch therefore produces no exit-2, so the tool call **proceeds** — a **fail-open of a security guard**. The original guard named `python`, which stock macOS and many Linux distros do not ship (only `python3`) — a fail-open on the very POSIX hosts the safety model relies on. Naming `python3` alone closes that, but reopens the symmetric hole flagged by the Codex **P1** review: a POSIX layout that exposes the supported 3.11+ runtime as bare `python` with **no** `python3` regresses back to an unresolvable launch — the guard silently stops intercepting, and `hygiene.py apply` is no longer forced through the guard's final `ask`. ## Fix - **`plugins/disk-hygiene/skills/clean/SKILL.md`** — the guard now registers **two** PreToolUse launch entries under the same matcher, `command: "python3"` and `command: "python"`, each running the same `destructive_guard.py`. All matching hooks run in parallel, so whichever name resolves to a 3.11+ runtime enforces — a host exposing the runtime under **either** name is guarded. This is fail-closed in every direction: an unresolvable name is a non-blocking launch error contributing nothing, and a legacy `python` 2.x entry crashes on modern syntax (also non-blocking); the sibling entry still guards. It cannot introduce a new fail-open — worst case it over-*denies* (fail-closed) on a pathological host where the two names resolve to genuinely distinct real interpreters (`resolve(strict=True)` collapses the common `python`→`python3` symlink to one path, so both `allow` the same engine call there). - **Regression probe** — `test_skill_hook_interpreters_cover_python3_and_python_and_resolve` in `test_hygiene.py`, modeled on the sibling `test_skill_hook_passes_authorized_data_root_flag_matching_constant` seam test. It statically locks the frontmatter launch names to exactly `{python3, python}`, anchored on each entry's `destructive_guard.py` args line so an unrelated future `command:` key elsewhere in SKILL.md cannot be matched by accident (closing the parser-fragility advisory from the `claude[bot]` reviews). The runtime half asserts every Python 3 name that resolves is 3.11+, tolerates a legacy 2.x `python` (its py3 sibling enforces), and skips only where no name yields a runnable 3.11+ interpreter — the documented residual host gap, not a regression. - **rule 6e — no false guarantee** — the `## Gotchas` bullet and CHANGELOG entry caveat that enforcement is only as strong as interpreter resolution. Residual: on a host where **neither** name resolves to a runnable 3.11+ interpreter (e.g. a python.org-only Windows install exposing only `py`) the launch still fails open on the manual PowerShell deletion lane. That does **not** open a silent auto-delete path — engine `apply` is unsupported on Windows and macOS, runs only after the guard's `ask`, and the manual-handoff human approval plus the consumer's baseline permission policy still gate every deletion — but it is defense-in-depth lost, not preserved. `/disk-hygiene:setup check` reports which interpreters resolve. - **Version** — `disk-hygiene` `0.4.2` → `0.4.3`, with a top-inserted CHANGELOG entry. ### Why two entries, not a launcher script or shell form Exec form already runs without a shell, so the guard needs no Git Bash/PowerShell — switching to shell form to do `python3 || python` resolution would *newly* depend on a shell and regress the PowerShell-only-Windows case. A "tiny launcher" must itself be launched by some interpreter name (same bootstrap problem), and exec form on Windows requires `command` to resolve to a real executable such as a `.exe` — a `.sh` launcher cannot be the `command`. Two hook entries were **initially rejected** here citing double-run on machines with both names. That call is now reversed: it optimized against redundant execution, but the Codex P1 confirmed a real fail-*open* on a supported runtime layout, which is the dominating concern for a safety guard. Double-run of a fail-closed guard is safe redundancy (both entries run the identical decision function; the common `python`→`python3` symlink resolves to one path, so they agree); a silently unguarded host is not. Registering both names is the minimal change that guards every host exposing the runtime under either name, with no new fail-open. ## Verification All commands run in the worktree. **Full suite (75 tests) via the plugin's cross-platform wrapper — pass:** ``` $ bash hygiene.test.sh ... Ran 75 tests in 2.910s OK (skipped=4) ``` **New regression test runs (not skipped) and passes:** ``` $ python3 -m unittest -v test_hygiene.GuardTests.test_skill_hook_interpreters_cover_python3_and_python_and_resolve Lock the guard's launch interpreters and prove a Python 3 name resolves. ... ok Ran 1 test in 0.091s OK ``` **Proof the guard launches under BOTH names and denies `rm -rf` (fail-closed):** ``` $ for i in python3 python; do echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf /tmp/example"}}' \ | "$i" destructive_guard.py --authorized-data-root /tmp/data | jq -r .hookSpecificOutput.permissionDecision; done deny deny ``` **Linters/format on changed files:** ``` $ markdownlint-cli2 plugins/disk-hygiene/skills/clean/SKILL.md plugins/disk-hygiene/CHANGELOG.md Summary: 0 error(s) $ ruff check plugins/disk-hygiene/skills/clean/scripts/test_hygiene.py All checks passed! ``` Closes #380 ## Related - Source issue: #380 (priority: high, area: security). - Sibling: merged PR #742 touched this same guard's argv (authorized-data-root resolution); this change is orthogonal (launch interpreter). - **Decision revised:** the earlier `python3`-only choice (`## Decision (Option A)` on #380) was re-derived after the Codex P1 review demonstrated it reopened a fail-open on a bare-`python`-only POSIX layout. Multi-name launch (both `python3` and `python`) is the corrected approach; the launcher / shell-form alternatives remain rejected as documented above. --- 🤖 Generated with [Claude Code](https://claude.com/claude-code)
…hell lane + via hook argv (#968) ## Summary The `disk_hygiene_enabled=false` kill switch ("audit-only mode") did not actually prevent deletions, for **two independent reasons**. Both are fixed here. - **B2** — the enabled flag never gated the PowerShell lane, so flagged deletion spellings still returned `ask` (and could be approved) with execution disabled. - **D3** — the kill switch was inert under the env-injection failure: the guard read the value from the hook process environment, which the runtime does not populate for a skill-frontmatter hook, so a configured `false` was silently overridden to enabled. ## Fix **B2 — gate the PowerShell lane** (`skills/clean/scripts/destructive_guard.py`). `main()` routed `tool_name == "PowerShell"` to `powershell_decision()` and returned *before* the `enabled` gate was computed (that computation was only reached on the Bash branch). The gate is now resolved once, before the tool-name branch, and threaded into `powershell_decision(command, enabled)`. A flagged mutation spelling (`Remove-Item`/`rm`/`del`/`::Delete`/recycle-bin) resolves against the kill switch via new helper `_powershell_mutation_verdict`: `ask` when enabled (preserves the manual-handoff prompt), **`deny` in audit-only mode**. Engine (`hygiene.py`) calls stay hard-denied regardless. **D3 — deliver the value through the hook argv** (`skills/clean/SKILL.md` + `destructive_guard.py`), mirroring the `--authorized-data-root ${CLAUDE_PLUGIN_DATA}` mechanism landed in #742 / #376. The frontmatter exec-form hook now passes `--disk-hygiene-enabled ${user_config.disk_hygiene_enabled}` in its `args` array. New `resolve_disk_hygiene_enabled()` reads the kill switch from that argv (generic `_argv_flag_value` extracted from `_argv_authorized_data_root`), treats a literal/unsubstituted placeholder or empty value as absent, and honors `CLAUDE_PLUGIN_OPTION_DISK_HYGIENE_ENABLED` **only as a fallback**. Absent every channel, the guard fails safe to `enabled=true` (guard active, every mutation still gated behind the final human prompt) — the pre-existing fail-safe default is preserved; the change is that a user who *set* `false` now has that value honored. Per the [plugins reference](https://code.claude.com/docs/en/plugins-reference), `${user_config.KEY}` is rejected only in *shell-form* fields; the sanctioned channel for exec-form hooks is `args` (or the env var) — exactly our hook shape. Docs updated to match: `SKILL.md` audit-only note, `reference/safety-model.md` PowerShell paragraph (previously stated deletions are always `ask`), `CHANGELOG.md`, and `plugin.json` `0.4.3 → 0.4.4`. ## Verification `python -m unittest test_hygiene` — **77 passed, 4 skipped** (Linux-only platform gates). New tests: - **B2**: `test_powershell_deletion_spellings_denied_in_audit_only_mode` — the five mutation spellings + `::Delete` return `deny` when disabled. The pre-existing `test_powershell_deletion_spellings_force_final_prompt` (enabled → `ask`) still passes, proving the enabled path is unchanged. - **D3 / acceptance**: `test_kill_switch_blocks_every_lane_via_argv_without_env` — the centerpiece: a configured `false` reaching the guard **only via argv, with the env var UNSET** (the exact env-injection failure) denies both a PowerShell `Remove-Item` **and** a Bash `apply`. `test_kill_switch_enabled_via_argv_without_env_still_gates` confirms an argv `true` (env unset) yields `ask`, proving the argv value — not a hardcoded default — drives the decision. - Precedence/seam: `test_resolve_disk_hygiene_enabled_precedence` (argv-over-env both directions, case/whitespace-tolerant `false`, placeholder/empty → env fallback, no-channel → fail-safe enabled), `test_argv_flag_value_parses_both_arg_spellings`, and `test_skill_hook_passes_disk_hygiene_enabled_flag_matching_constant` (locks the frontmatter-args ↔ guard-constant seam). Linters: `ruff check` — **All checks passed**; `markdownlint-cli2` on the 3 changed markdown files — **0 errors**; `check-changelog-parity.sh --check` and `--check-bump origin/main` — **pass** (`0.4.4` entry present). (`ruff format` is not the repo's gate — `origin/main` is not `ruff format`-clean either, so it is not run to avoid unrelated churn.) **Caveat (inherited, unverifiable without the live harness):** the `!= "false"` comparison assumes a boolean userConfig serializes to the string `"false"`. This is the *same assumption the pre-existing env-var gate already made* (`CLAUDE_PLUGIN_OPTION_...` was parsed identically), so it is no new regression; and live-harness substitution of `${user_config.disk_hygiene_enabled}` into exec-form `args` rests on the plugins-reference guarantee (same substitution pass as `${CLAUDE_PLUGIN_DATA}`, already proven to resolve in this exact position) and cannot be exercised without the running harness. Closes #382 ## Related - Source: #382. - #376 & #742 — the env-injection root cause and the `--authorized-data-root` argv precedent this builds on. --- 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Summary
The
disk-hygienecleanskill declaresdestructive_guard.pyas a PreToolUseskill-frontmatter hook. The guard read its authorized data root only from the
CLAUDE_PLUGIN_DATAenvironment variable, but the runtime does not inject thatvariable into a skill-frontmatter hook's process environment. As a result
--data-rootnever validated and thescan/preview/applyengine lane failedclosed on every guarded invocation — on all platforms — making the plugin's core
engine unreachable through its sanctioned Bash lane.
Fix
Root cause: the guard depended on environment-variable injection, but per the
plugins reference inline
placeholder substitution (
${CLAUDE_PLUGIN_DATA}) resolves in hook arguments"anywhere the placeholder appears," and the reference explicitly recommends exec
form with
argsso each path is passed as one argument. Inline substitution wasalready proven to work in this exact frontmatter context (
${CLAUDE_PLUGIN_ROOT}resolves — the guard script path loads and executes); only environment injection
was missing.
skills/clean/SKILL.mdfrontmatter hook now passes the root as aruntime-substituted argument:
--authorized-data-root ${CLAUDE_PLUGIN_DATA}.destructive_guard.pyresolves its authority from that hook argv(
resolve_authorized_data_root), honoringCLAUDE_PLUGIN_DATAonly as afallback. The authority is resolved once in
main()and threaded explicitlydown the classification chain instead of being read from
os.environdeep inthe leaf validator.
CLAUDE_PLUGIN_DATA" mechanism are corrected inSKILL.mdandreference/safety-model.mdto describe the runtime-substituted argument.Security property is unchanged: the authority comes from the guard's own
process argv (harness-substituted, model-unforgeable), a channel distinct from
the model-supplied
--data-rootinside the gated Bash command; the latter isvalidated against the former and can never become its own authority.
Verification
python -m unittest test_hygiene— 71 passed, 4 skipped (Linux-only platformgates). Adds 4 tests: argv authorizes matching
--data-rootwithCLAUDE_PLUGIN_DATAabsent from the environment; mismatch denied; deny whenneither argv nor env supplies authority; argv-over-env precedence with
literal-placeholder fallback.
CLAUDE_PLUGIN_DATAremoved from the environment — the exact failing scenario):
--authorized-data-root <root>→permissionDecision: allowpermissionDecision: deny,citing the now-accurate "neither the
--authorized-data-roothook argumentnor
CLAUDE_PLUGIN_DATA" guidance.scripts/check-changelog-parity.sh --checkand--check-bump origin/main— pass(version bumped
0.4.0→0.4.1,## [0.4.1]entry added).markdownlint-cli2on the three changed markdown files — 0 errors.Not covered by units: live-harness end-to-end substitution of
${CLAUDE_PLUGIN_DATA}in a skill-frontmatter hook'sargs. This rests on theplugins-reference guarantee (same inline-substitution pass as
${CLAUDE_PLUGIN_ROOT}, which empirically resolves in this exact position) — itcannot be exercised without the running harness.
Closes #376
Related
tracked): disk-hygiene: disk_hygiene_enabled kill switch does not gate the PowerShell lane and is inert under the env-injection failure #382 (the
disk_hygiene_enabledkill switch readsCLAUDE_PLUGIN_OPTION_DISK_HYGIENE_ENABLEDfrom the hook env, also absent —~2 lines once this argv plumbing exists), disk-hygiene: doc corrections — Windows mislabeled 'full', false CLAUDE_PLUGIN_DATA premise repeated, PowerShell-lane gaps #386 (the broader doc sweep beyond
the two sentences this change falsifies), disk-hygiene: setup:check reports healthy while the engine is dead (misses #376 env-injection and bare-python failures) #385 (
setup:checkliveness probe).liveness instance; opened as a draft pending that umbrella's decision.