Severity: IMPORTANT (assigned by an audit agent; uncalibrated — not re-graded by a human).
Two variable-substitution defects. Both make a documented mechanism silently inert.
Defect 1 — the allowed-tools grant uses a variable that is not substituted there
skills/audit/SKILL.md:7:
allowed-tools:
- Bash(bash ${CLAUDE_PLUGIN_ROOT}/skills/audit/scripts/audit-fleet.sh *)
Current official docs (skills page, fetched 2026-07-30,
https://code.claude.com/docs/en/skills#available-string-substitutions) state that Claude Code
substitutes ${CLAUDE_SKILL_DIR} and ${CLAUDE_PROJECT_DIR} in two places: the skill's markdown
content, and Bash rules in the allowed-tools frontmatter. ${CLAUDE_PLUGIN_ROOT} is not named in
that claim; the plugins reference documents it for hook/monitor/MCP/LSP JSON command fields.
If it is not substituted, the rule stays a literal ${CLAUDE_PLUGIN_ROOT}/... string, never matches
the real invocation, and the skill's one permission grant is dead — the collector prompts for
permission on every run, in a skill designed for unattended fleet sweeps.
Fix: swap to ${CLAUDE_SKILL_DIR}. For a skill at skills/audit/, ${CLAUDE_SKILL_DIR} is that
directory, so the rule becomes Bash(bash ${CLAUDE_SKILL_DIR}/scripts/audit-fleet.sh *) — documented
to substitute in allowed-tools, and byte-identical in effect.
Two open questions, explicitly not asserted
- Whether this grant would survive
auto mode even if it substituted. The permission-modes page
documents that on entering auto mode, "broad allow rules that grant arbitrary code execution are
dropped", naming "wildcarded interpreters like Bash(python*)". Whether Bash(bash <path> *)
classifies as a wildcarded interpreter (the interpreter token is bash) or as a narrow rule (it
pins one script path) is not something we verified — flagging it because a fleet audit is
exactly the workload that runs in auto mode. The sibling skill
claude-config:audit-permission-grants exists for precisely this check and is the right probe.
- Whether quoting affects matching. The observed invocation quoted the path
(bash "C:/Users/.../audit-fleet.sh" --root "D:/repos") while the rule pattern is unquoted. Current
docs say nothing about that interaction. Unverified, not asserted.
- We also cannot confirm whether the observed session received this grant at all: the collector
ran, which is consistent with either a matching grant or an unrelated permission allowance.
Defect 2 — the project-config rung is unreachable, and the fallback is undisclosed
skills/audit/scripts/audit-fleet.sh:307 probes a project-scoped config, and :385 falls back to
"${CLAUDE_PROJECT_DIR:-$PWD}".
CLAUDE_PROJECT_DIR is not set in the Bash tool's environment. Verified directly:
$ printenv CLAUDE_PROJECT_DIR
$ echo $?
1
The docs guarantee that variable for command hooks, MCP stdio servers, plugin LSP servers, and monitor
commands — not for arbitrary Bash tool invocations. Since this collector runs via the Bash tool,
the variable is absent, so:
- The project-scoped rung of the config ladder resolves against nothing and is effectively dead.
- The zero-argument fallback silently becomes
$PWD — the shell's current directory, which for an
agent session is incidental and can be anywhere.
- Neither fact appears in the report. The header claims "current-project scope" regardless (see the
report-text issue).
Fix: don't read the env var from inside the script. Pass it in from the skill body, where the
documented substitution applies — e.g. invoke with --repo "${CLAUDE_PROJECT_DIR}". Then the rung
works by the documented mechanism instead of an undocumented one. And when no scope resolves, say so
rather than silently adopting $PWD.
Why these are grouped
Same root cause: the plugin relies on variable substitution in two places where the documented
contract does not provide it. Both fixes are frontmatter/invocation changes with no logic change.
Environment
repo-fleet-hygiene 0.7.0, marketplace install melodic-software
- Claude Code on Windows 11 Pro 10.0.26200, Bash tool (Git Bash)
- Docs fetched 2026-07-30
- Observed 2026-07-29/30
Severity: IMPORTANT (assigned by an audit agent; uncalibrated — not re-graded by a human).
Two variable-substitution defects. Both make a documented mechanism silently inert.
Defect 1 — the
allowed-toolsgrant uses a variable that is not substituted thereskills/audit/SKILL.md:7:Current official docs (skills page, fetched 2026-07-30,
https://code.claude.com/docs/en/skills#available-string-substitutions) state that Claude Code
substitutes
${CLAUDE_SKILL_DIR}and${CLAUDE_PROJECT_DIR}in two places: the skill's markdowncontent, and Bash rules in the
allowed-toolsfrontmatter.${CLAUDE_PLUGIN_ROOT}is not named inthat claim; the plugins reference documents it for hook/monitor/MCP/LSP JSON
commandfields.If it is not substituted, the rule stays a literal
${CLAUDE_PLUGIN_ROOT}/...string, never matchesthe real invocation, and the skill's one permission grant is dead — the collector prompts for
permission on every run, in a skill designed for unattended fleet sweeps.
Fix: swap to
${CLAUDE_SKILL_DIR}. For a skill atskills/audit/,${CLAUDE_SKILL_DIR}is thatdirectory, so the rule becomes
Bash(bash ${CLAUDE_SKILL_DIR}/scripts/audit-fleet.sh *)— documentedto substitute in
allowed-tools, and byte-identical in effect.Two open questions, explicitly not asserted
automode even if it substituted. The permission-modes pagedocuments that on entering
automode, "broad allow rules that grant arbitrary code execution aredropped", naming "wildcarded interpreters like
Bash(python*)". WhetherBash(bash <path> *)classifies as a wildcarded interpreter (the interpreter token is
bash) or as a narrow rule (itpins one script path) is not something we verified — flagging it because a fleet audit is
exactly the workload that runs in auto mode. The sibling skill
claude-config:audit-permission-grantsexists for precisely this check and is the right probe.(
bash "C:/Users/.../audit-fleet.sh" --root "D:/repos") while the rule pattern is unquoted. Currentdocs say nothing about that interaction. Unverified, not asserted.
ran, which is consistent with either a matching grant or an unrelated permission allowance.
Defect 2 — the project-config rung is unreachable, and the fallback is undisclosed
skills/audit/scripts/audit-fleet.sh:307probes a project-scoped config, and:385falls back to"${CLAUDE_PROJECT_DIR:-$PWD}".CLAUDE_PROJECT_DIRis not set in the Bash tool's environment. Verified directly:The docs guarantee that variable for command hooks, MCP stdio servers, plugin LSP servers, and monitor
commands — not for arbitrary Bash tool invocations. Since this collector runs via the Bash tool,
the variable is absent, so:
$PWD— the shell's current directory, which for anagent session is incidental and can be anywhere.
report-text issue).
Fix: don't read the env var from inside the script. Pass it in from the skill body, where the
documented substitution applies — e.g. invoke with
--repo "${CLAUDE_PROJECT_DIR}". Then the rungworks by the documented mechanism instead of an undocumented one. And when no scope resolves, say so
rather than silently adopting
$PWD.Why these are grouped
Same root cause: the plugin relies on variable substitution in two places where the documented
contract does not provide it. Both fixes are frontmatter/invocation changes with no logic change.
Environment
repo-fleet-hygiene0.7.0, marketplace installmelodic-software