Verify the disk-hygiene plugin's runtime prerequisites and platform posture for this machine. Use when: 'set up disk-hygiene', 'configure disk-hygiene', 'is disk-hygiene working', a clean run reported a missing prerequisite, or before a first audit on a new machine. Actions: check (read-only verification, default) | apply (resolve what check found). Re-runnable and safe.
Installs into .claude/skills of the current project.
Are you the author of Setup?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/melodic-software-setup-f9fa2677)
---
description: "Verify the disk-hygiene plugin's runtime prerequisites and platform posture for this machine. Use when: 'set up disk-hygiene', 'configure disk-hygiene', 'is disk-hygiene working', a clean run reported a missing prerequisite, or before a first audit on a new machine. Actions: check (read-only verification, default) | apply (resolve what check found). Re-runnable and safe."
argument-hint: "[check|apply]"
user-invocable: true
disable-model-invocation: true
---
## Purpose
Thin check-centric setup per the uniform setup contract (`docs/plugin-philosophy.md`
"Setup is explicit and repeatable" in the marketplace repository): `check` inspects and
reports, `apply` resolves. This plugin owns no consumer-project configuration, targets
and modes arrive as `/disk-hygiene:clean` arguments, and the only tunable is the native
`userConfig` toggle, so `apply` is pure guidance and writes nothing.
Action routing: no argument or `check` runs the check; `apply` runs the check first, then
points at each remediation. Both are non-interactive, never prompt when the action is given.
## `check` (read-only)
The clean skill and its bundled scripts (`${CLAUDE_PLUGIN_ROOT}/skills/clean/`) are the
single source of truth for what the plugin requires per platform.
**Read it first.** Probe what it actually does, don't recite this file. Then run each probe via
Bash and report a PASS/FAIL/INFO table with one remediation line per FAIL. Do not modify anything.
The toggle is an audit-only kill switch, not a short-circuit: the guard registers
unconditionally and still scans with the toggle off, so most prerequisite absences keep their
FAIL semantics regardless of the toggle. Only the execution-tier tools the audit lane never
invokes (step 4's `lsof`-class probes) downgrade from FAIL to INFO when the toggle is
disabled, report those informationally and note that re-enabling restores the FAIL semantics.
A missing `git` stays FAIL either way: the audit lane itself calls it, and its absence degrades
the VCS-tracked-content evidence the plugin's fail-closed posture depends on.
Every step-1 and step-2 failure likewise stays FAIL with the toggle disabled.
Audit-only mode is *enforced by* the guard, every guard surface
depends on a Python 3 interpreter resolving (every surface through
`hooks/run-python-hook.sh`, which tries `python3`, then `python`, then `py -3`), and a
guard that never runs can neither read nor enforce the configured `false`, so the fail-open
is most dangerous in exactly this
configuration. That covers an **exhausted** interpreter ladder, every rung absent, a stub, or
`indeterminate`, a rung whose version probe then fails to launch at all (a corrupt or
zero-length binary outside `WindowsApps`, a broken shim, a permission error), *and* an
interpreter that starts but reports a version below the parsed floor. It does **not** cover a
stubbed `python3` alongside a working `python` or `py -3`: the launcher skips the stub, every
guard launches, and that is a WARN (see step 2), not a failure to downgrade.
Launching the version probe proves only that something executes, not that it can run
the guard's own source: Python 3.6, for example, rejects the guard's
`from __future__ import annotations` and exits without a deny, which PreToolUse treats as
non-blocking, the same silent fail-open through a different door. Unproven guard execution
fails closed like every other guard-relevant unknown in this plugin.
1. **Node exec-form launcher registration**. All three registrations (both wired hooks in
`hooks/hooks.json` and the skill-scoped belt in `skills/clean/SKILL.md` frontmatter) must set
`command` to `node` and put `hooks/exec-bash.mjs`, then `hooks/run-python-hook.sh` and that
script's arguments, in `args`. `node` resolves to `node.exe`. FAIL if `command` is bare `bash`,
`sh`, or `python3`, or if the script path is `command` with no `args`: on Windows bare `bash` is
the WSL relay and a `.sh` path is not a spawnable executable, the launch fails, and a failed hook
launch is non-blocking, so the guard silently enforces nothing. Also FAIL if `exec-bash.mjs` or
the launcher is missing.
Claim: exec form spawns `command` as an executable with `args` and no shell, and on Windows
`command` must be a real executable such as `node.exe`.
Basis: `https://code.claude.com/docs/en/hooks`, "Exec form and shell form".
As of: 2026-09-28.
Recheck: that page changes the Windows executable sentence, or stops ignoring `shell` when
`args` is set.
**`node` and bash**, probed with the Bash tool, not through the launcher. FAIL if
`command -v node` finds nothing: every guard and detector row runs `node`, so none launches and no guard
is enforced. FAIL if no bash resolves in the launcher's own order, which the header comment of
`${CLAUDE_PLUGIN_ROOT}/hooks/exec-bash.mjs` lists per platform (read it; do not recite it). On
Windows the remediation is either `CLAUDE_CODE_GIT_BASH_PATH`, a Git for Windows install, or a
`bash.exe` on `PATH` outside `System32`, `Sysnative` and `WindowsApps`; elsewhere, a `bash` on
`PATH`. Both FAILs stay FAIL with the toggle disabled, like the interpreter ladder. State the
consequence in the remediation: the hook fails to launch, the user sees a hook error notice, the
guard is not enforced, the call proceeds, and the model is not told. A missing `node` cannot be
reported by the launcher or the Stop detector, which both need `node` to start.
Claim: Claude Code's native binary does not ship or invoke Node, so `node` on `PATH` is a
separate prerequisite.
Basis: `https://code.claude.com/docs/en/setup`, fetched 2026-09-29.
As of: 2026-09-29.
Recheck: that page lists Node as bundled or required, or `hooks/hooks.json` stops using `node`
as `command`.
2. **Python floor on `PATH`**. The interpreter used by scanning, validation, the
guard, and cleanup. (The guard registers on two surfaces: a plugin-level engine gate
that acts only on engine-referencing commands, and the skill-frontmatter belt that
Claude Code keeps armed for the rest of the session after `/disk-hygiene:clean` is
invoked. Both register unconditionally and resolve the kill switch by
reading `disk_hygiene_enabled` from managed settings first, then the user `settings.json`.) The required version has one origin: the `MIN_PYTHON`
constant in `${CLAUDE_PLUGIN_ROOT}/skills/clean/scripts/hygiene.py`, parse it from
there (`grep -m1 '^MIN_PYTHON' …`) and probe the interpreter against that value; do not
recite a version number from this file or the README. FAIL if absent or older, naming
the parsed floor in the remediation; the plugin never downloads a runtime. Report the
absolute interpreter path (guarded engine calls must use the same absolute interpreter
the guard reports. Bash aliases and functions cannot substitute).
On Windows, confirm the name the guard resolves is real BEFORE anything executes it,
including this floor check's own version probe. `python3` is the first rung of the ladder
`hooks/run-python-hook.sh` walks for every guard surface; on stock Windows it resolves to a
zero-length `WindowsApps\python3.exe` App Execution Alias that opens the Microsoft Store
(or hangs) instead of running an interpreter, and executing that name from setup pops the
Store instead of probing. The stub does not stop any guard by itself: the launcher skips it
and falls through to `python`, then `py -3`. **The ladder, not the first
rung, is the verdict.** A host with real Python installed without "Add to PATH" but with the
`py` launcher has a stubbed `python3` and a perfectly working guard; failing it would report a
healthy install as broken and send the operator to reinstall Python. So the alias probe is
**diagnostic input**. It says what the first rung is, and keeps setup from executing it,
while the verdict comes from resolving the ladder and checking the selected interpreter
against the parsed floor.
Order of operations: (a) locate the resolution without executing it (`Get-Command python3`
/ `command -v python3`, locating is inspection; running is not); (b) classify it with the
bundled inspect-only probe, launched via an interpreter that is NOT the bare name
`python3` (`py -3`, `python`, or an absolute interpreter path, any interpreter already
proven real):
`"<python>" "${CLAUDE_PLUGIN_ROOT}/skills/setup/scripts/python3_alias_probe.py"`; if no
such interpreter exists **or the one you chose emits no JSON verdict**, apply the probe's own
portable signal directly in PowerShell. Proven real is not the same as able to run the probe:
a pre-3.7 interpreter rejects its `from __future__ import annotations` and a legacy `python`
2.x fails earlier still, each before anything is classified, so a machine whose only
alternate launcher is Python 3.6 reaches the verdict through PowerShell, not by having no
launcher at all. The signal: a zero-length file under a `WindowsApps` path component is the stub
(`(Get-Item -Force (Get-Command python3).Source)` → `Length` 0 plus a `ReparsePoint`
attribute); (c) resolve the ladder in the launcher's own order, skipping any rung the probe
classified as a stub: `python3` only when its verdict is `ok`, then `python`, then `py -3`.
The version probe may execute a rung only once that rung is not a stub, that is the whole
point of (b), and it is why the bare name `python3` is never executed on a
`store-alias-stub` verdict. Report the absolute path of the first rung that runs and meets
the floor.
**FAIL when the ladder is exhausted or below the floor**. No rung resolves, or the one that
does reports a version under the parsed `MIN_PYTHON`. That is the real fail-open: the guard
cannot run, and it emits neither exit 2 nor a `deny`. Distinguish the two for the remediation
wording, an interpreter that starts and reports a version below the floor is a floor miss
(name the parsed floor), one that fails to launch at all is an absent-interpreter failure
(the plugin never downloads a runtime). Both keep the FAIL under a disabled toggle: a
below-floor interpreter is not proven able to execute the guard's source, so audit-only mode
is unenforceable there too. `indeterminate` on **every** rung is the same case, identity the
probe could not read anywhere on the ladder is uncertainty about whether the guard can launch
at all, and fails closed like every other guard-relevant unknown in this plugin.
**PASS with a WARN when the ladder resolves a supported interpreter but `python3` is the
stub.** Every guard launches, so nothing is failing open. State the residual plainly: the
operator's own bare `python3` still opens the Microsoft Store, and guarded engine calls must
use the absolute interpreter path reported above rather than that name. Offer the same
remediation as an optional tidy-up, not as a fix for a broken install, disable the `python3`
App execution alias (Settings > Apps > Advanced app settings > App execution aliases), or put
real Python ahead of WindowsApps on `PATH`. A bare `command -v python3` success is not
evidence on its own, it matches the stub too.
3. **Git**. `command -v git`. Conditional per the README: optional for ordinary trees,
required when a target contains or sits inside a Git worktree. Report presence as INFO
with that conditionality stated; absence is only a FAIL for worktree-containing targets.
4. **Platform posture**. Detect the current OS family and report its documented lane per
the README, keeping the audit and execution lanes visibly separate: Windows (full
**audit**: `lstat` reparse + Win32, never UAC; engine **execution unsupported**.
`preview` reports `execution-platform-unsupported` as a per-candidate blocker, removal is
a manual, per-path Recycle-Bin handoff only after an execution request and explicit
approval), Linux (full audit; execution when
`/proc/self/mountinfo` is readable, `lsof` needed only for that optional execution
lane, absent `lsof` is INFO with the reduced-capability note), macOS (audit/report
only by design; manual Trash handoff only after an execution request and explicit approval. INFO, not a defect).
5. **Execution kill switch**. Resolve the effective `disk_hygiene_enabled` value
deterministically; never present an assumed value as the configured one. Run the bundled
probe with the step-2 interpreter:
`"<python>" "${CLAUDE_PLUGIN_ROOT}/skills/setup/scripts/kill_switch_probe.py"`
and report its `effective` value together with its `source` (`configured` vs `default`).
When the probe says `degraded: true`, report that the configured value could not be read
and that default `true` is being assumed, an assumption, never the configured value. The
body token `${user_config.disk_hygiene_enabled}` is at most a cross-check: if it expanded
to a boolean that contradicts the probe, report the discrepancy instead of silently
preferring either channel (the probe sees user settings only; managed settings or a
`--settings` flag can carry a value the probe cannot see).
6. **Plugin registration**. INFO: confirm the plugin is enabled for this project
(`/plugin` → Installed) rather than parsing settings files.
## `apply` (idempotent)
Run `check`, then for each FAIL point at the resolution. Every prerequisite is a system
tool or an OS capability, so `apply` installs nothing and writes nothing, it only points:
- missing/old Python: the platform's own install channel for the floor `check` parsed from
the engine's `MIN_PYTHON`; never a plugin download.
- missing git (worktree targets): platform install instructions.
- missing `node` or no bash the launcher accepts: the platform's own install channel for Node.js
or Git for Windows, or on Windows `CLAUDE_CODE_GIT_BASH_PATH` naming an existing `bash.exe`.
- toggle off: reconfigure through Claude Code's native flow, per the marketplace's
plugin-reconfiguration convention, which owns the verified-version record
(<https://github.com/melodic-software/claude-code-plugins/blob/main/docs/conventions/plugin-reconfiguration/README.md>):
interactive `/plugin configure disk-hygiene@<marketplace>` any time, or headless
`claude plugin install disk-hygiene@<marketplace> -s <scope> --config disk_hygiene_enabled=true`
(repeatable per key). Against an already-installed plugin it prints `already installed` **and
still writes the value**. Do **not** uninstall to reconfigure: uninstalling drops this plugin's
entire stored `pluginConfigs` entry, resetting every option in the README's Options reference
to its manifest default. `-s` defaults to `user`; pass the scope `claude plugin list` reports
for this plugin, and run from that project's directory for a `project`/`local` scope, or the
rerun adds a second install record at the scope passed and enables the plugin there; the
value itself always lands in user settings. A rejected value prints a warning yet exits 0,
so read the output. This skill never writes user settings or
`pluginConfigs`. Afterwards rerun `check` in a **fresh session**, because the rendered
`${user_config.*}` is injected at skill load and each hook receives its
`CLAUDE_PLUGIN_OPTION_*` from an environment fixed at session start, so a same-session `check`
still reports the OLD value; report the observed effective value, never an unobserved change.
Re-running `apply` after everything passes changes nothing and reports "already configured".
## What this skill does NOT do
- Run an audit or cleanup, that is `/disk-hygiene:clean`.
- Write the plugin cache, Claude Code user settings, or `pluginConfigs`.
- Install any tool or runtime, during either `check` or `apply`, guidance only.