You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
audit-permission-grants keeps its doctrine in reference/criteria.md, a pinned copy of the permission-rule-hygiene convention. The copy still carries Version: 1.0.0 / Last updated: 2026-07-14
while the convention has moved five times since, and there is no version handshake in either direction —
so the drift is silent and is the cause of the rest of this cluster rather than a peer of it. #2259
synced P2's rationale down without bumping either stamp, which is the drift reproducing in real time.
Downstream of that: the P1 finding message scolds a shape Anthropic's own docs ship and omits the
turn-scope limiter that changes what the grant even means; the package-manager extension reaches past
the documented category with no honesty marker, while the fixed-path-interpreter case two sections away
does carry one; the operative remediation lands in a file the detector cannot read; the plugin bin/
capability that would make the prescribed pattern achievable is never mentioned; the route-out has no
branch for the sibling being absent; and the description over-triggers into that sibling's territory.
A13 — scripts/permission-rule-check.sh:172 — the P1 message tells the author their grant "is an interpreter/runner-led grant, not the portable bare-name pattern" (a shape the docs themselves ship) and never mentions that the grant is turn-scoped.
A18 — reference/criteria.md:38-40 vs :52-54 — the bare-package-manager-wildcard extension reaches past the documented "package-manager run commands" category with no marker, while the fixed-path-interpreter case in the same file carries one.
A6 — SKILL.md:43-44, reference/criteria.md:109-111, scripts/permission-rule-check.sh:293 vs :280-281 — every P3 remediation says the operative rule must go in ~/.claude/settings.json; no code path in the script ever reads ~/.claude/settings.json or $HOME.
A10 — plugins/claude-config/bin does not exist; grep -rn 'bin/' across the skill returns only shebangs and test fixtures — the plugin bin/ capability is never mentioned, in the skill whose whole remedy is a bare name on PATH.
A17 — SKILL.md:25-31 and reference/criteria.md:83-89 — both route out to sibling audit skills unconditionally, with no "if that skill is not installed, do X" branch.
A9 — SKILL.md:2 with disable-model-invocation: false at :5 — the description's trigger list opens with 'check permission rules' and includes 'audit allow rules', both generic enough to match the sibling claude-config:audit; the negative boundary is body-only and invisible at discovery time.
citing the convention at an unpinned URL (criteria.md:9). The convention has moved five times
since that date: 4c7c21a6 (#1277), 6c1f9e8f (#1399), e3bfa56a (#1861), 29f0759f (#2060), 9d9fe485 (#2180). Concrete divergences, not hypothetical:
Convention README.md:94-100 discloses that the bare-package-manager extension reaches past the doc's category. criteria.md:38-40 still ships the extension with no such marker (A18).
Convention README.md:28-67 carries an auto-mode-default section neither criteria.md nor SKILL.md mentions.
Corroboration that the drift bites: plugins/repo-fleet-hygiene/skills/audit/SKILL.md:6 ships Bash(bash ${CLAUDE_SKILL_DIR}/scripts/audit-fleet.sh *) — the fleet was already using the variable criteria.md did not name and whose P1 message tells the author to abandon.
Fix shape, and it is smaller than the originating item assumed. The item's remediation said land the
doctrine in the convention first. At HEAD the convention is already ahead on every point, so this
reduces to: sync criteria.md down, bump Version / Last updated, and add a handshake line naming
the convention commit it was synced from. No convention edit is required.
A13 — the P1 message is wrong in two ways
scripts/permission-rule-check.sh:172 at HEAD:
[[ -n "$m" ]] && emit warning P1 "$src" "'$m' is an interpreter/runner-led grant, not the portable bare-name pattern; Claude Code drops the broad forms of this shape (blanket, package-manager runners, and wildcarded/globbed-target interpreters) on entering auto mode. Expose the guarded script as a bare PATH command and allow that, e.g. Bash(babysit_merge.sh:*)."
The style scolding is contradicted by Anthropic's own page.https://code.claude.com/docs/en/skills, fetched 2026-08-11, ships allowed-tools: Bash(python3 *) in its codebase-visualizer tutorial.
The turn-scope limiter is missing, and it is material. Same page: "The allowed-tools field grants permission for the listed tools during the turn that invokes the skill … The grant clears when you send your next message, even though the skill content stays in context; invoking the skill again re-applies it for that turn." grep -rni 'next message|turn-scoped|turn that invokes' across the whole skill → zero hits.
The security framing the message should carry is the docs' own: "Review project skills before trusting a repository, since a skill can grant itself broad tool access."
This is the message the authors of every fleet grant read when they go to fix one, which is why it
outranks the other text rows.
A18 — an undisclosed extension, next to a disclosed one
reference/criteria.md:38-40 flags bare package-manager wildcards with the rationale inline and no
marker that this reaches past the documented category:
package-manager grant — a runner subcommand (`Bash(npx *)`, `Bash(uvx *)`, `Bash(pipx run *)`,
`Bash(pnpm dlx *)`, …) or a bare package-manager wildcard (`Bash(npm:*)`, `Bash(npm *)`,
`Bash(pnpm:*)`, `Bash(yarn:*)`), which grants arbitrary execution via `npm exec` / lifecycle scripts —
Contrast the fixed-path-interpreter case at criteria.md:52-54, which does carry one: "…is flagged
as the same authoring anti-pattern even where the doc's dropped-category wording does not clearly reach
it". https://code.claude.com/docs/en/permission-modes, fetched 2026-08-11: "…Package-manager run
commands … Narrow rules like Bash(npm test) carry over." The check reaches bare package-manager
wildcards, which is broader. The extension is defensible; the silence about it is the defect, and the
asymmetry with the marker two sections away is what makes it a defect rather than a style choice.
Drop the item's stated justification for this row. It argued that the context7/setup finding was
"the concrete demonstration that A18 is material." That argument does not survive — that flag was a
regex over-reach, filed separately in this batch. A18 stands on the internal asymmetry alone.
A6 — the skill cannot audit the scope it prescribes
The prescription: SKILL.md:43-44 ("the bare-name rule must land in user-global ~/.claude/settings.json,
which a skill or plugin cannot write"), reference/criteria.md:109-111 ("the operative rule must be
added by the operator to ~/.claude/settings.json"), and scripts/permission-rule-check.sh:293 (the
P3 finding message, same sentence).
The scan surface: scripts/permission-rule-check.sh:280-281 reads only "$ROOT/.claude/settings.json"
and "$ROOT/.claude/settings.local.json". No code path anywhere in the script reads ~/.claude/settings.json or $HOME — and after #2249 the root can no longer fall through to a home
directory even accidentally. A genuine closed loop, not an oversight in one file.
The P3 premise itself is confirmed: https://code.claude.com/docs/en/plugins-reference, fetched
2026-08-11, components table — "Settings | settings.json | Default configuration applied when the
plugin is enabled. Only the agent and subagentStatusLine keys are currently supported". Note the
word "currently", which both of the skill's restatements (criteria.md:109-110, scripts/permission-rule-check.sh:293) drop — a fidelity nit to fold in, and a reason to re-check that
quote on a harness bump.
A10 — bin/ is never mentioned, and the naive fix would re-open #1398
plugins/claude-config/bin does not exist. grep -rn 'bin/' across the skill returns only shebangs
and .venv/bin/python test fixtures. The capability is real — https://code.claude.com/docs/en/plugins-reference, fetched 2026-08-11: "Executables | bin/ |
Executables added to the Bash tool's PATH. Files here are invokable as bare commands in any Bash tool
call while the plugin is enabled."
The originating framing is over-optimistic and must not be carried through. It presents bin/ as
"what makes the skill's own bare-name-on-PATH advice achievable." This repo's own convention says the
opposite at docs/conventions/permission-rule-hygiene/README.md:177-179: "Known gap — step 1's plugin bin/ delivery is unreliable, not absent … a helper whose only permission story is bin/-on-PATH has no
allow rule it can depend on." A fix that documents bin/ as the answer without that caveat re-opens #1398. Mention bin/, and mention the Known gap in the same breath.
A17 — route-out with no absent-seam branch
SKILL.md:25-31 and reference/criteria.md:83-89 both route out to the sibling audit skills
unconditionally, with no "if that skill is not installed, do X" branch. Both targets exist in this
repository (plugins/claude-config/skills/audit/, plugins/claude-memory/skills/audit/), so the
coherence half of the claim holds and the fallback half is simply absent. This is a conditional gap —
it bites only where the sibling plugin is not installed — and it couples to A6: the second half of the
row is that neither sibling covers the user-global scope either.
A9 — the description over-triggers into the sibling's territory
SKILL.md:2 — the description's trigger list opens with 'check permission rules' and includes 'audit allow rules', both generic enough to match the sibling claude-config:audit skill's remit
(baseline deny/ask presence, overly broad patterns, plugin drift). The negative boundary is body-only — SKILL.md:25-31, "Scope boundary (route out)" — and invisible at discovery time. disable-model-invocation: false at SKILL.md:5 means the model can select on the description alone. (No harness doc governing description-based selection was fetched; this row stands on the two surfaces
above, not on a harness-behavior claim.)
A7a — the documented cheap fix reached P2 and not P1
When this was filed, ${CLAUDE_SKILL_DIR} appeared nowhere in plugins/claude-config/ — zero hits
across SKILL.md, criteria.md, the detector, and the tests — so the skill prescribed a remedy a tier
more expensive than the documented zero-operator-action pattern. #2259 fixed part of that: the variable
now appears at reference/criteria.md:82, :86, :99 and in the P2 finding message at scripts/permission-rule-check.sh:175.
The residue is P1, which is the check that matters more here.reference/criteria.md:56-57:
**Recommend**: expose the helper as a bare command on PATH and allow the bare name narrowly. An
`Agent` rule has no bare-PATH analog — remove or re-scope it, or run outside auto mode.
Every interpreter-led fleet grant trips P1, not P2, so P1's Recommend is the line their authors read —
and it still names only the pattern #1398 says is unavailable on the measured platform. The variable
also still appears zero times in SKILL.md and zero times in scripts/permission-rule-check.test.sh, so nothing pins that a Bash(${CLAUDE_SKILL_DIR}/scripts/x.sh *) rule is not flagged.
https://code.claude.com/docs/en/skills, fetched 2026-08-11: "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," with the canonical example allowed-tools: Bash(${CLAUDE_SKILL_DIR}/scripts/render.sh *); and "${CLAUDE_SKILL_DIR} — The
directory containing the skill's SKILL.md file. For plugin skills, this is the skill's subdirectory
within the plugin, not the plugin root." That second clause is load-bearing for anyone writing the P1
recommendation and holds today.
Severity: MED (cluster highest; A9, A10, A17, A18 are LOW) · Provenance: AUDITOR_VERIFIED. Two rationale corrections are the validation pass's own and are stated inline: A10's bin/-is-the-answer framing is partially falsified by this repo's own Known gap, and A18's context7 justification does not survive.
Origin: handoff-inbox item 20260811-024628-claude-config-audit-permission-grants-defects-and-fleet-grant-hygiene
Ledger: .work/handoff-inbox-batch-4/ledgers/I10-permission-grants-fleet.md § A6, A7a, A9, A10, A13, A14, A17, A18
Verified against repo HEAD 9b34a82a. Line anchors re-derived at that commit — #2259 added 44 lines to criteria.md and 52 to the detector.
audit-permission-grantskeeps its doctrine inreference/criteria.md, a pinned copy of thepermission-rule-hygieneconvention. The copy still carriesVersion: 1.0.0 / Last updated: 2026-07-14while the convention has moved five times since, and there is no version handshake in either direction —
so the drift is silent and is the cause of the rest of this cluster rather than a peer of it. #2259
synced P2's rationale down without bumping either stamp, which is the drift reproducing in real time.
Downstream of that: the P1 finding message scolds a shape Anthropic's own docs ship and omits the
turn-scope limiter that changes what the grant even means; the package-manager extension reaches past
the documented category with no honesty marker, while the fixed-path-interpreter case two sections away
does carry one; the operative remediation lands in a file the detector cannot read; the plugin
bin/capability that would make the prescribed pattern achievable is never mentioned; the route-out has no
branch for the sibling being absent; and the description over-triggers into that sibling's territory.
Rows
reference/criteria.md:3-4and:9—Version: 1.0.0,Last updated: 2026-07-14, citing the convention at an unpinnedmainURL. The convention has moved five times since; no handshake exists in either direction. fix(claude-config): drop P2's unsupported no-expansion claim, refuse an unresolvable scan root, and key the postures report per project #2259 synced content down without touching either stamp.scripts/permission-rule-check.sh:172— the P1 message tells the author their grant "is an interpreter/runner-led grant, not the portable bare-name pattern" (a shape the docs themselves ship) and never mentions that the grant is turn-scoped.reference/criteria.md:38-40vs:52-54— the bare-package-manager-wildcard extension reaches past the documented "package-manager run commands" category with no marker, while the fixed-path-interpreter case in the same file carries one.SKILL.md:43-44,reference/criteria.md:109-111,scripts/permission-rule-check.sh:293vs:280-281— every P3 remediation says the operative rule must go in~/.claude/settings.json; no code path in the script ever reads~/.claude/settings.jsonor$HOME.plugins/claude-config/bindoes not exist;grep -rn 'bin/'across the skill returns only shebangs and test fixtures — the pluginbin/capability is never mentioned, in the skill whose whole remedy is a bare name on PATH.SKILL.md:25-31andreference/criteria.md:83-89— both route out to siblingauditskills unconditionally, with no "if that skill is not installed, do X" branch.SKILL.md:2withdisable-model-invocation: falseat:5— the description's trigger list opens with'check permission rules'and includes'audit allow rules', both generic enough to match the siblingclaude-config:audit; the negative boundary is body-only and invisible at discovery time.reference/criteria.md:56-57,SKILL.md,scripts/permission-rule-check.test.sh—${CLAUDE_SKILL_DIR}now appears in P2 and in the P2 finding message (fix(claude-config): drop P2's unsupported no-expansion claim, refuse an unresolvable scan root, and key the postures report per project #2259), but P1'sRecommendstill prescribes only bare-name-on-PATH, and the variable appears nowhere inSKILL.mdor the test suite. P1 is the check every fleet grant trips.Evidence
A14 — a pinned copy against an unpinned upstream
reference/criteria.md:3-4at HEAD:citing the convention at an unpinned URL (
criteria.md:9). The convention has moved five timessince that date:
4c7c21a6(#1277),6c1f9e8f(#1399),e3bfa56a(#1861),29f0759f(#2060),9d9fe485(#2180). Concrete divergences, not hypothetical:${CLAUDE_SKILL_DIR}/${CLAUDE_PROJECT_DIR}substitution carve-out and the${CLAUDE_PLUGIN_ROOT}-is-inert ruling; fix(claude-config): drop P2's unsupported no-expansion claim, refuse an unresolvable scan root, and key the postures report per project #2259 has now brought the substitution half down into P2 — without bumpingVersionorLast updated, so a reader cannot tell which doctrine the file reflects.README.md:94-100discloses that the bare-package-manager extension reaches past the doc's category.criteria.md:38-40still ships the extension with no such marker (A18).README.md:177-179carries thebin/Known gap.criteria.mdP1 Recommend still prescribes bare-name-on-PATH unconditionally (that half is claude-config(audit-permission-grants): remediation prescribes a bare-name-on-PATH pattern that is unavailable on the measured platform #1398).README.md:28-67carries an auto-mode-default section neithercriteria.mdnorSKILL.mdmentions.Corroboration that the drift bites:
plugins/repo-fleet-hygiene/skills/audit/SKILL.md:6shipsBash(bash ${CLAUDE_SKILL_DIR}/scripts/audit-fleet.sh *)— the fleet was already using the variablecriteria.mddid not name and whose P1 message tells the author to abandon.Fix shape, and it is smaller than the originating item assumed. The item's remediation said land the
doctrine in the convention first. At HEAD the convention is already ahead on every point, so this
reduces to: sync
criteria.mddown, bumpVersion/Last updated, and add a handshake line namingthe convention commit it was synced from. No convention edit is required.
A13 — the P1 message is wrong in two ways
scripts/permission-rule-check.sh:172at HEAD:allowed-tools: Bash(python3 *)in its codebase-visualizer tutorial.allowed-toolsfield grants permission for the listed tools during the turn that invokes the skill … The grant clears when you send your next message, even though the skill content stays in context; invoking the skill again re-applies it for that turn."grep -rni 'next message|turn-scoped|turn that invokes'across the whole skill → zero hits.This is the message the authors of every fleet grant read when they go to fix one, which is why it
outranks the other text rows.
A18 — an undisclosed extension, next to a disclosed one
reference/criteria.md:38-40flags bare package-manager wildcards with the rationale inline and nomarker that this reaches past the documented category:
Contrast the fixed-path-interpreter case at
criteria.md:52-54, which does carry one: "…is flaggedas the same authoring anti-pattern even where the doc's dropped-category wording does not clearly reach
it". https://code.claude.com/docs/en/permission-modes, fetched 2026-08-11: "…Package-manager run
commands … Narrow rules like
Bash(npm test)carry over." The check reaches bare package-managerwildcards, which is broader. The extension is defensible; the silence about it is the defect, and the
asymmetry with the marker two sections away is what makes it a defect rather than a style choice.
Drop the item's stated justification for this row. It argued that the
context7/setupfinding was"the concrete demonstration that A18 is material." That argument does not survive — that flag was a
regex over-reach, filed separately in this batch. A18 stands on the internal asymmetry alone.
A6 — the skill cannot audit the scope it prescribes
The prescription:
SKILL.md:43-44("the bare-name rule must land in user-global~/.claude/settings.json,which a skill or plugin cannot write"),
reference/criteria.md:109-111("the operative rule must beadded by the operator to
~/.claude/settings.json"), andscripts/permission-rule-check.sh:293(theP3 finding message, same sentence).
The scan surface:
scripts/permission-rule-check.sh:280-281reads only"$ROOT/.claude/settings.json"and
"$ROOT/.claude/settings.local.json". No code path anywhere in the script reads~/.claude/settings.jsonor$HOME— and after #2249 the root can no longer fall through to a homedirectory even accidentally. A genuine closed loop, not an oversight in one file.
The P3 premise itself is confirmed: https://code.claude.com/docs/en/plugins-reference, fetched
2026-08-11, components table — "Settings |
settings.json| Default configuration applied when theplugin is enabled. Only the
agentandsubagentStatusLinekeys are currently supported". Note theword "currently", which both of the skill's restatements (
criteria.md:109-110,scripts/permission-rule-check.sh:293) drop — a fidelity nit to fold in, and a reason to re-check thatquote on a harness bump.
A10 —
bin/is never mentioned, and the naive fix would re-open #1398plugins/claude-config/bindoes not exist.grep -rn 'bin/'across the skill returns only shebangsand
.venv/bin/pythontest fixtures. The capability is real —https://code.claude.com/docs/en/plugins-reference, fetched 2026-08-11: "Executables |
bin/|Executables added to the Bash tool's
PATH. Files here are invokable as bare commands in any Bash toolcall while the plugin is enabled."
The originating framing is over-optimistic and must not be carried through. It presents
bin/as"what makes the skill's own bare-name-on-PATH advice achievable." This repo's own convention says the
opposite at
docs/conventions/permission-rule-hygiene/README.md:177-179: "Known gap — step 1's pluginbin/delivery is unreliable, not absent … a helper whose only permission story is bin/-on-PATH has noallow rule it can depend on." A fix that documents
bin/as the answer without that caveat re-opens#1398. Mention
bin/, and mention the Known gap in the same breath.A17 — route-out with no absent-seam branch
SKILL.md:25-31andreference/criteria.md:83-89both route out to the siblingauditskillsunconditionally, with no "if that skill is not installed, do X" branch. Both targets exist in this
repository (
plugins/claude-config/skills/audit/,plugins/claude-memory/skills/audit/), so thecoherence half of the claim holds and the fallback half is simply absent. This is a conditional gap —
it bites only where the sibling plugin is not installed — and it couples to A6: the second half of the
row is that neither sibling covers the user-global scope either.
A9 — the description over-triggers into the sibling's territory
SKILL.md:2— the description's trigger list opens with'check permission rules'and includes'audit allow rules', both generic enough to match the siblingclaude-config:auditskill's remit(baseline deny/ask presence, overly broad patterns, plugin drift). The negative boundary is body-only —
SKILL.md:25-31, "Scope boundary (route out)" — and invisible at discovery time.disable-model-invocation: falseatSKILL.md:5means the model can select on the description alone.(No harness doc governing description-based selection was fetched; this row stands on the two surfaces
above, not on a harness-behavior claim.)
A7a — the documented cheap fix reached P2 and not P1
When this was filed,
${CLAUDE_SKILL_DIR}appeared nowhere inplugins/claude-config/— zero hitsacross
SKILL.md,criteria.md, the detector, and the tests — so the skill prescribed a remedy a tiermore expensive than the documented zero-operator-action pattern. #2259 fixed part of that: the variable
now appears at
reference/criteria.md:82,:86,:99and in the P2 finding message atscripts/permission-rule-check.sh:175.The residue is P1, which is the check that matters more here.
reference/criteria.md:56-57:Every interpreter-led fleet grant trips P1, not P2, so P1's
Recommendis the line their authors read —and it still names only the pattern #1398 says is unavailable on the measured platform. The variable
also still appears zero times in
SKILL.mdand zero times inscripts/permission-rule-check.test.sh, so nothing pins that aBash(${CLAUDE_SKILL_DIR}/scripts/x.sh *)rule is not flagged.https://code.claude.com/docs/en/skills, fetched 2026-08-11: "Claude Code substitutes
${CLAUDE_SKILL_DIR}and${CLAUDE_PROJECT_DIR}in two places: the skill's markdown content, and Bashrules in the
allowed-toolsfrontmatter," with the canonical exampleallowed-tools: Bash(${CLAUDE_SKILL_DIR}/scripts/render.sh *); and "${CLAUDE_SKILL_DIR}— Thedirectory containing the skill's
SKILL.mdfile. For plugin skills, this is the skill's subdirectorywithin the plugin, not the plugin root." That second clause is load-bearing for anyone writing the P1
recommendation and holds today.
Adjacent, deliberately not duplicated
criteria.md— read claude-config(audit-permission-grants): remediation prescribes a bare-name-on-PATH pattern that is unavailable on the measured platform #1398 first.bin/delivery-gap tracker the convention cites.Provenance
Severity: MED (cluster highest; A9, A10, A17, A18 are LOW) · Provenance: AUDITOR_VERIFIED. Two rationale corrections are the validation pass's own and are stated inline: A10's
bin/-is-the-answer framing is partially falsified by this repo's own Known gap, and A18'scontext7justification does not survive.Origin: handoff-inbox item
20260811-024628-claude-config-audit-permission-grants-defects-and-fleet-grant-hygieneLedger:
.work/handoff-inbox-batch-4/ledgers/I10-permission-grants-fleet.md§ A6, A7a, A9, A10, A13, A14, A17, A18Verified against repo HEAD
9b34a82a. Line anchors re-derived at that commit — #2259 added 44 lines tocriteria.mdand 52 to the detector.