Skip to content

repo-fleet-hygiene: allowed-tools grant uses a variable not substituted there, and the project-config rung is unreachable #1798

Description

@kyle-sexton

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:

  1. The project-scoped rung of the config ladder resolves against nothing and is effectively dead.
  2. The zero-argument fallback silently becomes $PWD — the shell's current directory, which for an
    agent session is incidental and can be anywhere.
  3. 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

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions