This defect class has now shipped three times in disk-hygiene alone, and each fix was verified by a test that encoded the defect as its contract.
The class
An exec-form hook (args present) resolves command as a bare PATH lookup. Per the hooks reference: "On Windows, exec form requires command to resolve to a real executable such as a .exe." Two Windows spellings fail:
"command": "bash" — resolves to the WSL relay System32\bash.exe before Git Bash; dies with execvpe(/bin/bash) failed.
"command": "python3" — commonly the zero-length WindowsApps\python3.exe App Execution Alias stub.
A failed hook launch is non-blocking, so a PreToolUse gate built this way silently enforces nothing.
History
plugins/claude-config/skills/audit/reference/audit-checklist.md Category D already carries this as an error row — and disk-hygiene violated it anyway, because a checklist a human reads is not a gate.
Proposal
Add scripts/check-hook-exec-form.sh plus a self-test, modelled directly on the existing scripts/check-hook-userconfig-argv.sh gate (same scope rules: default hooks/hooks.json, manifest-pointed hook configs, inline manifest hooks objects — and it should additionally cover SKILL.md / agent frontmatter hooks, which the userconfig gate does not). Fail on any exec-form hook whose command contains no path separator and is not on a small allowlist of names that are real executables on every targeted platform (node, python via an absolute path, etc.), with a documented escape-hatch allowlist file that can only shrink.
Wire it as its own CI job with the self-test-first, fail-closed pattern the userconfig-argv gate uses.
Related
This defect class has now shipped three times in
disk-hygienealone, and each fix was verified by a test that encoded the defect as its contract.The class
An exec-form hook (
argspresent) resolvescommandas a barePATHlookup. Per the hooks reference: "On Windows, exec form requirescommandto resolve to a real executable such as a.exe." Two Windows spellings fail:"command": "bash"— resolves to the WSL relaySystem32\bash.exebefore Git Bash; dies withexecvpe(/bin/bash) failed."command": "python3"— commonly the zero-lengthWindowsApps\python3.exeApp Execution Alias stub.A failed hook launch is non-blocking, so a
PreToolUsegate built this way silently enforces nothing.History
"command": "bash"+args; disk-hygiene: the destructive-operation guard has never launched — 73 recorded runs, all hook_non_blocking_error #1416 closedCOMPLETEDwhile the guard stayed dead.python3in exec form (latent Windows fail-open) #2568 tracks the remainingpython3instance.plugins/claude-config/skills/audit/reference/audit-checklist.mdCategory D already carries this as anerrorrow — and disk-hygiene violated it anyway, because a checklist a human reads is not a gate.Proposal
Add
scripts/check-hook-exec-form.shplus a self-test, modelled directly on the existingscripts/check-hook-userconfig-argv.shgate (same scope rules: defaulthooks/hooks.json, manifest-pointed hook configs, inline manifesthooksobjects — and it should additionally cover SKILL.md / agent frontmatter hooks, which the userconfig gate does not). Fail on any exec-form hook whosecommandcontains no path separator and is not on a small allowlist of names that are real executables on every targeted platform (node,pythonvia an absolute path, etc.), with a documented escape-hatch allowlist file that can only shrink.Wire it as its own CI job with the self-test-first, fail-closed pattern the userconfig-argv gate uses.
Related
python3in exec form (latent Windows fail-open) #2568 — the skill-hook instance