Defect
Seven skill allowed-tools grants across five plugins are interpreter-led (Bash(bash <path>…)) and,
in four cases, written with ${CLAUDE_PLUGIN_ROOT} — which is not substituted in allowed-tools
frontmatter, so those grants are inert literal strings that never match.
| row |
skill |
grant at HEAD |
state |
| B1 |
prototype/explore-directions |
Bash(bash ${CLAUDE_PLUGIN_ROOT}/scripts/detect-ecosystems.sh:*) |
inert |
| B2 |
prototype/pressure-test |
same |
inert |
| B3 |
code-tidying/tidy |
Bash(bash ${CLAUDE_PLUGIN_ROOT}/skills/tidy/scripts/open-pr-count.sh:*) |
inert |
| B4 |
repo-hygiene/clean |
Bash(bash ${CLAUDE_PLUGIN_ROOT}/skills/clean/scripts/*) |
inert + wildcarded target |
| B5 |
repo-fleet-hygiene/audit |
Bash(bash ${CLAUDE_SKILL_DIR}/scripts/audit-fleet.sh *) |
dead — quote mismatch |
| B6 |
code-tidying/audit-comment-residue |
Bash(bash *audit-comment-residue/scripts/detect.sh*) |
works, anti-pattern |
| B7 |
docs-hygiene/audit-noise |
Bash(bash *audit-noise/scripts/detect.sh*) |
works, anti-pattern |
The corrected mechanism — the originally prescribed fix is falsified
The originating item prescribed "drop bash from the rule." That is wrong and would convert an
anti-pattern into a dead grant. bash is not one of the wrappers Claude Code strips before matching
Bash rules (the stripped set is timeout, time, nice, nohup, stdbuf, command, builtin,
noglob — https://code.claude.com/docs/en/permissions), and this repo's own convention already
records that at docs/conventions/permission-rule-hygiene/README.md:218-220. Every one of the seven
skill bodies invokes its script through bash "<path>", so a rule without bash stops matching
the command the body actually runs.
The correct fix is a paired body + rule rewrite: change the body to invoke the bundled script
directly (shebang + exec bit, both already present and verified to survive plugin-cache install), and
write the rule against that same string using ${CLAUDE_SKILL_DIR}, which is substituted in
allowed-tools (https://code.claude.com/docs/en/skills).
Two traps
Evidence at HEAD (685dd381)
plugins/prototype/skills/explore-directions/SKILL.md:6 / :17,
plugins/prototype/skills/pressure-test/SKILL.md:6 / :17,
plugins/code-tidying/skills/tidy/SKILL.md:6 / :18,
plugins/code-tidying/skills/audit-comment-residue/SKILL.md:6 / :17 / :49,
plugins/repo-hygiene/skills/clean/SKILL.md:5-6 (+ body lines 40, 102–166 and context/*.md),
plugins/repo-fleet-hygiene/skills/audit/SKILL.md:6 / :67,
plugins/docs-hygiene/skills/audit-noise/SKILL.md:6 / :17 / :62.
The shipped detector reports all seven as warning [P1] at HEAD.
Checklist
Not in scope
context7/setup's Bash(npm view ctx7 version*) (ledger row B8) is NOT_REPRODUCED — a
fully-pinned rule the convention explicitly exempts at README.md:94-100. It is flagged only by a
P1_ERE regex over-reach, filed separately by the verifier as ledger row A19 against
claude-config.
Provenance
Defect
Seven skill
allowed-toolsgrants across five plugins are interpreter-led (Bash(bash <path>…)) and,in four cases, written with
${CLAUDE_PLUGIN_ROOT}— which is not substituted inallowed-toolsfrontmatter, so those grants are inert literal strings that never match.
prototype/explore-directionsBash(bash ${CLAUDE_PLUGIN_ROOT}/scripts/detect-ecosystems.sh:*)prototype/pressure-testcode-tidying/tidyBash(bash ${CLAUDE_PLUGIN_ROOT}/skills/tidy/scripts/open-pr-count.sh:*)repo-hygiene/cleanBash(bash ${CLAUDE_PLUGIN_ROOT}/skills/clean/scripts/*)repo-fleet-hygiene/auditBash(bash ${CLAUDE_SKILL_DIR}/scripts/audit-fleet.sh *)code-tidying/audit-comment-residueBash(bash *audit-comment-residue/scripts/detect.sh*)docs-hygiene/audit-noiseBash(bash *audit-noise/scripts/detect.sh*)The corrected mechanism — the originally prescribed fix is falsified
The originating item prescribed "drop
bashfrom the rule." That is wrong and would convert ananti-pattern into a dead grant.
bashis not one of the wrappers Claude Code strips before matchingBash rules (the stripped set is
timeout,time,nice,nohup,stdbuf,command,builtin,noglob— https://code.claude.com/docs/en/permissions), and this repo's own convention alreadyrecords that at
docs/conventions/permission-rule-hygiene/README.md:218-220. Every one of the sevenskill bodies invokes its script through
bash "<path>", so a rule withoutbashstops matchingthe command the body actually runs.
The correct fix is a paired body + rule rewrite: change the body to invoke the bundled script
directly (shebang + exec bit, both already present and verified to survive plugin-cache install), and
write the rule against that same string using
${CLAUDE_SKILL_DIR}, which is substituted inallowed-tools(https://code.claude.com/docs/en/skills).Two traps
quoted path. A frontmatter-only change regresses them from "works but is an anti-pattern" to
"does not work."
while the body quotes it. repo-fleet-hygiene: allowed-tools grant uses a variable not substituted there, and the project-config rung is unreachable #1798 fixed only the
${CLAUDE_PLUGIN_ROOT}to${CLAUDE_SKILL_DIR}halfand explicitly parked quoting as "Unverified, not asserted."
Evidence at HEAD (
685dd381)plugins/prototype/skills/explore-directions/SKILL.md:6/:17,plugins/prototype/skills/pressure-test/SKILL.md:6/:17,plugins/code-tidying/skills/tidy/SKILL.md:6/:18,plugins/code-tidying/skills/audit-comment-residue/SKILL.md:6/:17/:49,plugins/repo-hygiene/skills/clean/SKILL.md:5-6(+ body lines 40, 102–166 andcontext/*.md),plugins/repo-fleet-hygiene/skills/audit/SKILL.md:6/:67,plugins/docs-hygiene/skills/audit-noise/SKILL.md:6/:17/:62.The shipped detector reports all seven as
warning [P1]at HEAD.Checklist
prototype/explore-directionsprototype/pressure-testcode-tidying/tidyrepo-hygiene/cleanrepo-fleet-hygiene/auditcode-tidying/audit-comment-residuedocs-hygiene/audit-noiseNot in scope
context7/setup'sBash(npm view ctx7 version*)(ledger rowB8) is NOT_REPRODUCED — afully-pinned rule the convention explicitly exempts at
README.md:94-100. It is flagged only by aP1_EREregex over-reach, filed separately by the verifier as ledger rowA19againstclaude-config.Provenance
20260811-024628-claude-config-audit-permission-grants-defects-and-fleet-grant-hygiene.work/handoff-inbox-batch-4/ledgers/I10-permission-grants-fleet.md— "Half B — 8 fleetallowed-toolsgrant defects (6 plugins)" (rows B1–B8), plus the cross-cutting section "the findingthat reframes half B"
repo-fleet-hygienevariable half only), source-control:babysit-prs: plugin bin/ not on Bash-tool PATH — guarded wrapper bare names unresolvable (blocks narrow allow-rule match) #843 (pluginbin/delivery gap, which is why relocation rather than
bin/is the remedy)