Skip to content

docs(permission-rule-hygiene): record the undelivered plugin bin/-on-PATH capability#1277

Merged
kyle-sexton merged 3 commits into
mainfrom
docs/843-plugin-bin-path-gap
Jul 25, 2026
Merged

docs(permission-rule-hygiene): record the undelivered plugin bin/-on-PATH capability#1277
kyle-sexton merged 3 commits into
mainfrom
docs/843-plugin-bin-path-gap

Conversation

@kyle-sexton

@kyle-sexton kyle-sexton commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

No linked issue

Summary

The permission-rule-hygiene convention's correct pattern tells helper authors to expose a guarded
code-execution helper as a bare command on the Bash tool's PATH, and names a plugin's bin/
directory as the way to do that after plugin migration. Upstream documents that behavior. It does not
happen. This PR records the gap in the convention that depends on it, so no future helper is designed
around a capability that is not there.

This is a docs-only change under docs/conventions/. It ships no code, touches no plugin, and does
not close #843 — the capability itself is upstream.

Fix

docs/conventions/permission-rule-hygiene/README.md gains a Known gap section after the correct
pattern, recording:

  • The measured behavior — which <wrapper> exits 1 and no plugin directory of any kind appears on
    PATH, on Windows 11 / Git Bash, Claude Code v2.1.219, with the owning plugin installed at user
    scope and reported enabled.
  • Its scope: harness-wide, not a packaging defect in one plugin. A second, unrelated installed
    plugin that also ships a bin/ is equally absent from PATH. The files are committed 100755,
    present in the install cache, with correct shebangs, and the feature predates the measured harness.
  • The two consequences for a helper author: invoke by bundled path (resolving ${CLAUDE_PLUGIN_ROOT}
    in skill/agent content, which is not exported to the Bash tool's own environment), and expect the
    call to reach the classifier every time — bash is not one of the wrappers Claude Code strips
    before matching, so a rule for a bash <path> … command has to name bash, making it
    interpreter-led, which is the document's own anti-pattern 1. The section is deliberately asymmetric
    about what follows from that: the documented drop categories reach the wildcarded-target form
    (Bash(bash <path>*)), while whether they reach a fixed-path form (Bash(bash <fixed-path>:*)) is
    not stated upstream, so that shape is an anti-pattern on convention grounds rather than a
    confirmed drop.
  • Why the two attractive substitutes are not substitutes: a ~/.local/bin shim pins a
    version-numbered install path that changes on every plugin update; an env.PATH entry in user
    settings does reach the Bash tool's shell but carries the same version-pinned rot, plus the hazard
    of overriding PATH wholesale.
  • One candidate recorded as untested rather than rejected: Bash rules accept a wildcard in any
    position, including leading, so a rule anchored on the wrapper's own name rather than on the
    interpreter could reach the bundled-path invocation without naming one. The section then states the
    two things that must be established first — and the first of them disqualifies the obvious
    spelling
    : the documented bundled-path form quotes the path, so the character following the
    wrapper name is a closing quote rather than a space, and a candidate shaped Bash(*<wrapper-name> *)
    does not match it. The candidate has to be derived from the exact command string operators are
    told to run. The second is that whether a leading-wildcard rule survives auto mode is undocumented —
    the drop list enumerates blanket rules, wildcarded interpreters, package-manager runners, and
    Agent rules, and is silent on a leading wildcard in the command position.

CHANGELOG.md for the convention records this as 1.1. The convention versions independently of any
plugin, so this carries no plugin.json bump.

Verification

Every claim in the new section is either a command run in this environment or a quote from a doc
fetched this session — no recall.

  • which / PATH probe, Git Bash, this machine:

    $ which source-control-babysit-merge ; echo $?
    which: no source-control-babysit-merge in (...)
    1
    $ echo "$PATH" | tr ':' '\n' | grep -i plugins   # no output
  • Harness-wide scope: the same probe against the caveman plugin's bin/ (a different
    marketplace, unrelated author) is equally absent, so this is not a source-control packaging
    defect.

  • Plugin state: claude plugin list reports source-control@melodic-software v0.26.0, scope
    user, ✔ enabled; bin/ is present in the install cache; git ls-files -s shows both wrappers at
    mode 100755.

  • Harness version: claude --version2.1.219, well past the release that introduced
    bin/-on-PATH.

  • Not a one-off reading: source-control:babysit-prs: plugin bin/ not on Bash-tool PATH — guarded wrapper bare names unresolvable (blocks narrow allow-rule match) #843 carries three independent reproductions of the same which failure
    from separate sessions on separate dates (the source-control:babysit-prs: 'source-control-babysit-resolve-thread' bare wrapper not on PATH (command not found), worker must fall back to raw GraphQL #484 origin, the triage lane's empirical check, and a
    later babysit cycle). This PR's probe is the fourth. All are the same machine and platform, so the
    record is repeated over time, not cross-platform — the section states the platform it was
    measured on and claims nothing beyond it.

  • env.PATH lever confirmed, then rejected on durability, not capability: a nested
    claude -p --settings '{"env":{"ZZ_PATH_PROBE":"REACHED"}}' run echoed [REACHED] from its Bash
    tool, so settings env does reach that shell. It is recorded as a non-substitute because of the
    version-pinned path, not because it doesn't work.

  • Upstream docs re-fetched this session, all still asserting the capability or bounding it:
    plugins-reference (Executables row; the
    agent/subagentStatusLine-only plugin settings.json; ${CLAUDE_PLUGIN_ROOT} exported to hook,
    MCP, and LSP subprocesses only),
    permissions (the stripped-wrapper
    list, which does not include bash), and
    permission-modes
    (the auto-mode drop list).

  • Gates: markdownlint-cli2 --config .markdownlint-cli2.jsonc over both changed files —
    Summary: 0 error(s). typos --config _typos.toml over the convention directory — exit 0.

  • Independent re-verification (fresh context, this branch's pushed head afa0356ed3, rationale
    withheld): every claim in the section re-tested against this machine and against the docs re-fetched
    again. The gap reproduces on harness 2.1.220 — one release past the version the section names,
    which strengthens the record rather than staling it. The bundled-path workaround was executed rather
    than asserted: bash "<cache>/0.26.2/bin/source-control-babysit-merge" reaches babysit_merge.py's
    argparse and exits 2 on the missing positional, proving the path resolves and the wrapper runs.
    plugins-reference still carries the Executables row verbatim, and anthropics/claude-code#42872
    quotes the v2.1.91 changelog introducing the feature. Full report in the PR comments.

Related

…PATH capability

The convention's correct pattern rests on a plugin's bin/ being added to
the Bash tool's PATH. That is documented upstream but does not happen on
Windows / Git Bash, so the bare-name allow rule the convention prescribes
has nothing to match and every guarded helper falls through to the
auto-mode classifier instead.

Record the measured behavior, its harness-wide scope, the two consequences
for helper authors, and why a ~/.local/bin shim is not a substitute, so no
future helper is designed around a capability that is not there.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SNNCY6Go6K3yNGYYCBFWJw
@claude

claude Bot commented Jul 25, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 1m 34s —— View job


PR Review

  • Gather context from PR metadata
  • Read changed files (CHANGELOG.md, README.md)
  • Check CLAUDE.md and any REVIEW.md guidelines
  • Review diff for correctness, factual accuracy, and alignment
  • Post final review feedback

Verdict: Approve with two advisory notes. This is a well-crafted, citation-backed documentation addition. The logic chains are sound, the epistemic framing is careful, and the section integrates cleanly with the existing document.

Factual / logical correctness

All claims in the new section are either directly cited or logically derived from cited sources:

  • bash not in the stripped-wrapper list → interpreter-led match → anti-pattern 1 → dropped in auto mode. The chain is correct and traceable to the permissions#process-wrappers and permission-modes pages.
  • ${CLAUDE_PLUGIN_ROOT} not exported to the Bash tool environment. Correctly scoped — the variable is substituted in skill/agent content (template substitution at load time), not available as a shell env var at runtime. The inline cite to the Environment variables row of plugins-reference is appropriate.
  • ~/.local/bin shim instability. The version-numbered-path argument is solid (plugin install paths include the version and change on update). The staging-clone risk is real, though somewhat edge-case without evidence that staging directories actually co-exist with release directories in the cache layout — this is a nit, not a blocker.

Platform scoping

The section correctly limits all claims to "Windows 11 / Git Bash, Claude Code v2.1.219." The "harness-wide" language means "not a packaging defect in one plugin" (two unrelated plugins equally absent), not "affects all OS platforms" — and the body text says "on this platform" consistently. That's the right epistemic posture.

Minor gap: The section never explicitly states that behavior on macOS/Linux is unverified. A helper author running on Linux today might read the heading, see "Windows / Git Bash", and assume they're fine to build on bin/-on-PATH. Adding one parenthetical like "(behavior on other platforms has not been independently verified)" to the opening sentence would close that ambiguity.

Section placement and CHANGELOG

  • The "Known gap" section between "The correct pattern" and "Sources" is the right location — it directly qualifies step 1's bin/ bullet without disturbing the anti-pattern or principle sections.
  • The 1.1 CHANGELOG entry is appropriately terse and matches the section title verbatim.
  • Versioning the convention independently of any plugin (no plugin.json bump) is correct per the PR description's own reasoning.

CLAUDE.md alignment

The fresh-docs mandate requires every claim to come from a fetched page. The section cites plugins-reference (Environment variables row), permissions#process-wrappers, and permission-modes#eliminate-prompts-with-auto-mode — all pages on the canonical list. The PR description confirms they were re-fetched this session.

Summary

Two advisory suggestions:

  1. Add "behavior on other platforms has not been verified" to the opening paragraph — prevents a non-Windows reader from treating the absence of a Windows-scoped heading as a green light.
  2. (Optional) Qualify the staging-clone shim risk with a note that this applies to install caches that co-locate staging and release directories — or drop that sentence if the cache layout doesn't guarantee it. The version-pinning argument alone is already a sufficient reason to reject the shim approach.

Neither is a blocker. The core record — measured behavior, scope, consequences, workaround — is accurate, cited, and actionable.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4ef41fc5a3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread docs/conventions/permission-rule-hygiene/README.md Outdated
…verified

The known-gap section asserted a `bash <path>` command "can only be matched
by an interpreter-led rule". Bash rules accept a wildcard in any position,
including leading, so a rule anchored on the wrapper name alone would match
it without naming an interpreter. Whether that shape survives auto mode is
not documented, so it belongs as a gated candidate rather than as an
absolute that overstates the evidence.

Also records `env.PATH` in user settings as a real but non-durable lever:
it does reach the Bash tool's shell, and carries the same version-pinned
path rot as a shim.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SNNCY6Go6K3yNGYYCBFWJw
@claude

claude Bot commented Jul 25, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 2m 42s —— View job


PR Review

  • Gather context from PR metadata
  • Read changed files (CHANGELOG.md, README.md)
  • Evaluate Codex P2 finding on auto-mode rule claim
  • Check prior review advisory note status
  • Post final review feedback

Verdict: Approve with one recommended fix. The core record is sound and well-cited. The Codex P2 finding identifies a genuine internal contradiction that should be corrected before merge. The two prior advisory notes remain open but are advisory.


Codex P2 finding — confirmed valid

The Codex reviewer flagged README.md:153–155:

bash is not one of the wrappers Claude Code strips before matching, so a rule for a bash <path> … command has to name bash — making it interpreter-led, i.e. anti-pattern 1, dropped on entering auto mode.

This contradicts criteria.md:43–49 (same repo), which explicitly acknowledges:

A grant that invokes one fixed script via an interpreter (Bash(bash <fixed-path>:*)) is flagged as the same authoring anti-pattern even where the doc's dropped-category wording does not clearly reach it; the fix (a bare PATH command) is the same.

So the criteria document flags Bash(bash <fixed-path>:*) as P1 as a convention call, not because it's confirmed dropped. The README is now claiming the opposite — that it definitively is dropped — without a probe to back it up. This matters because an operator who reads carefully will find two documents in the same repo giving conflicting signals about whether a fixed-script bash rule survives auto mode.

The second commit (9ea7e15) extended the section in a useful direction (adding env.PATH, adding the leading-wildcard untested candidate with explicit "unverified" qualifier) but the bash <path> → "dropped" claim was not fixed — only the bullet heading changed.

Recommended fix: Align with the untested-candidate paragraph's own epistemic pattern. Replace "anti-pattern 1, dropped on entering auto mode" with something like "anti-pattern 1 (criteria.md flags it as such even where the official drop-category wording does not clearly reach the fixed-script form); treat the invocation as reaching the classifier." That preserves the practical advice without overstating the verified behavior.

Fix this →


Prior advisory 1 — still open

The prior Claude review suggested adding an explicit callout that behavior on macOS/Linux is unverified. The section still scopes its claims to "Windows 11 / Git Bash" and uses "on this platform" throughout — which is the correct epistemic posture — but a non-Windows reader who notices only the heading might assume they're unaffected without any explicit prompt to verify for themselves. A single parenthetical at the opening sentence ("behavior on other platforms has not been independently verified") would close this gap. Advisory only.


Prior advisory 2 — still open, section usefully expanded

The staging-clone shim risk argument (line 161) was not qualified with a note about whether the cache layout actually co-locates staging and release directories. The second commit expanded the section in a useful direction (adding env.PATH as a second rejected substitute and the untested leading-wildcard candidate), but the staging-clone sentence still makes a claim about install-cache layout that wasn't probed. The version-pinning argument alone is fully sufficient to reject the ~/.local/bin shim; the staging-clone argument adds risk but without evidence it applies to the actual cache layout. Advisory only — the version-pinning reason is enough and the section is already well-motivated.


What this PR gets right

  • The section integrates cleanly between "The correct pattern" and "Sources" — exactly where a helper author would look after reading step 1's bin/ bullet.
  • Every major claim either has a citation, a console probe, or an explicit "unverified" qualifier (the leading-wildcard candidate). The untested-candidate paragraph is especially well-handled — naming the unknown precisely is more useful than silence.
  • env.PATH as a second rejected substitute (added in 9ea7e15) is a genuine addition; it's a plausible workaround a helper author might reach for, and documenting why it doesn't work saves them the trial.
  • CHANGELOG version bump is correct (convention versions independently of any plugin). Entry text matches the section title verbatim.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9ea7e15748

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread docs/conventions/permission-rule-hygiene/README.md
Comment thread docs/conventions/permission-rule-hygiene/README.md Outdated
…lly verified

Review found the section asserting more than the evidence carries in three
places.

The fixed-path `bash <path>` bullet declared the rule dropped on entering
auto mode. The documented drop categories reach the wildcarded-target form
but do not clearly reach a fixed-path target, and the audit criteria in this
same repo flag that shape on convention grounds rather than as a confirmed
drop. Stating it as confirmed left two documents in one repo disagreeing
about whether such a grant survives. The practical instruction is unchanged:
treat the call as reaching the classifier.

The leading-wildcard candidate named a concrete shape that cannot match the
invocation it targets: the documented bundled-path form quotes the path, so
the character after the wrapper name is a closing quote, not a space. The
candidate now records both gates it has to clear - matching the real command
string, and surviving auto mode - without asserting a replacement shape that
is equally unprobed.

Also states that macOS and Linux behavior is unverified, so a non-Windows
reader does not read the platform-scoped heading as a clearance, and drops
the staging-clone claim about install-cache layout, which was never probed
and which the version-pinning argument does not need.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kb58mL6JnfEos5zrhgn4GD
@claude

claude Bot commented Jul 25, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 2m 15s —— View job


PR Review

  • Gather context from PR metadata
  • Read latest commit diff (afa0356)
  • Check whether prior P2 finding was addressed
  • Evaluate outstanding Codex P2 finding (audit tool / SKILL.md recommendation)
  • Post final review feedback

Verdict: Approve. The latest commit (afa0356) addresses every finding raised in the prior two review cycles. The one outstanding Codex P2 concern is real but out of scope for a docs-only gap-recording PR.


What afa0356 fixed

Prior P2 (bash <path> → "dropped on entering auto mode")
Addressed. Lines 153–159 now correctly distinguish:

  • The wildcarded-target form (Bash(bash <path>*)) — the documented drop categories clearly reach it.
  • The fixed-path form (Bash(bash <fixed-path>:*)) — flagged as anti-pattern 1 on convention grounds (matching criteria.md lines 43–49) but not asserted to be a confirmed drop.

The practical instruction ("treat the call as reaching the classifier") is unchanged. The two documents in the repo no longer disagree.

Codex P2 #2 (leading-wildcard candidate fails before the auto-mode gate)
Addressed. Lines 175–178 now explicitly state that the bundled-path invocation quotes the path, so the character after the wrapper name is a closing quote — Bash(*<wrapper-name> *) does not match it and fails before the auto-mode question is reached. The section now records both gates to clear without asserting a replacement shape that is equally unprobed.

Advisory 1 (macOS/Linux unverified)
Addressed. Lines 128–129: "Behavior on macOS and Linux is unverified — probe there rather than reading a Windows-scoped heading as a clearance." Exactly what was asked for.

Advisory 2 (staging-clone shim claim unprobed)
Addressed. The staging-clone sentence is gone; line 164–165 retains only the version-pinning argument, which is sufficient and was actually verified.

CHANGELOG.md
The 1.1 entry is updated ("two specific unknowns" replacing "the specific unknown") — accurate reflection of the new two-gate structure.


The remaining Codex P2 concern (Codex second review, finding #1)

The finding was: permission-rule-check.sh flags Bash(bash ${CLAUDE_PLUGIN_ROOT}/...:*) as P1, and audit-permission-grants/SKILL.md lines 61–65 unconditionally recommends "replace with the bare-name-on-PATH pattern" — a recommendation that cannot be acted on today on Windows / Git Bash since the bin/-on-PATH feature isn't delivered.

This concern is real but out of scope for this PR. The audit tool's recommendation reflects the architecturally correct end state (the convention's "correct pattern"); the Known gap section now accurately documents why that end state isn't reachable on the measured platform. A helper author who reads both will understand: the audit is right that the bundled-path form is an anti-pattern; the gap section explains why the ideal fix is blocked. Closing that gap requires either the upstream capability to ship or the audit tool to detect the platform state — neither of which is achievable in a docs-only PR that deliberately does not close #843.

If the Codex concern is to be addressed, it belongs in a separate issue targeting permission-rule-check.sh or SKILL.md, not here.


Overall assessment

Every finding from the prior two cycles is resolved with no overcorrection. The section's epistemic posture — what was measured, what was inferred from cited docs, what is genuinely unknown — is now consistent throughout. Ready to merge.

@kyle-sexton

Copy link
Copy Markdown
Contributor Author

Review round — classification (commit afa0356e)

# Finding Classification Evidence Reacted
1 Codex P2 (README:155) / claude[bot] recommended fix — the section states categorically that a fixed-script Bash(bash <fixed-path>:*) rule is dropped on entering auto mode, contradicting criteria.md lines 43–49 VALID — fixed Confirmed the contradiction. criteria.md lists the wildcarded-target form Bash(bash <path>*) among the dropped categories but disclaims the fixed-path form: flagged "as the same authoring anti-pattern even where the doc's dropped-category wording does not clearly reach it". The README asserted the opposite without a probe. Fixed in afa0356e: the bullet now says the drop categories clearly reach the wildcarded-target form, that whether they reach a fixed-path form is not stated, and that the shape is an anti-pattern on convention grounds rather than a confirmed drop. Practical instruction is unchanged — treat the call as reaching the classifier. 👍
2 Codex P2 (README:151) — the audit's remediation (bare name on PATH) cannot work on the platform this section measures; audit criteria/reporting should recognize that state VALID (defer) — out of scope Classified in-thread on the finding itself. Real inconsistency, but the remediation lands in plugins/claude-config/skills/audit-permission-grants/, which this docs-only PR explicitly excludes. Needs its own change. 👍
3 Codex P2 (README:169) — Bash(*<wrapper-name> *) cannot match the documented invocation, because the character after the wrapper name is a closing quote, not a space VALID — fixed Verified against plugins/source-control/skills/babysit-prs/reference/safety.md: the documented form quotes the bundled path, so a candidate ending <wrapper-name> does not match it. Fixed in afa0356e by recording the constraint rather than asserting a corrected pattern — naming a replacement shape would swap one unprobed rule-matching claim for another, which this repo's fresh-docs mandate bars. The candidate now carries two explicit gates: matching the real command string, and surviving auto mode. 👍
4 claude[bot] advisory 1 — the section never states that macOS/Linux behavior is unverified, so a non-Windows reader may read the platform-scoped heading as a clearance VALID — fixed Open across two review rounds. afa0356e adds it to the opening sentence: behavior on macOS and Linux is unverified, probe there rather than reading a Windows-scoped heading as a clearance. 👍
5 claude[bot] advisory 2 — the staging-clone shim risk asserts install-cache layout that was never probed VALID — fixed Agreed the claim was unprobed, and version-pinning alone already rejects the ~/.local/bin shim. afa0356e drops the staging-clone clause rather than qualifying it. 👍

Still open

  • Finding 2 — needs a separate change against plugins/claude-config/skills/audit-permission-grants/.
  • Branch is behind main — reported, not refreshed (safe tier).

@kyle-sexton

Copy link
Copy Markdown
Contributor Author

Independent verification — VERDICT: PASS-WITH-FINDINGS

Audited by a fresh context that did not write this branch, given the artifact and #843's requirement only, rationale withheld. Because the branch documents rather than fixes, the audit asked three questions instead of "does the fix work": is every factual claim true; is documenting-rather-than-fixing justified; does it over- or under-state the limitation.

No claim in the diff is falsified. Findings are calibration precision, one un-isolated root cause, a stale PR body, and pre-existing adjacent drift.

Empirical re-test on this machine (Claude Code 2.1.220, Win 11 / Git Bash)

$ which source-control-babysit-merge; echo $?
which: no source-control-babysit-merge in (/mingw64/bin:/usr/bin:/c/Users/KyleSexton/bin:...)
1

$ which source-control-babysit-resolve-thread; echo $?
which: no source-control-babysit-resolve-thread in (...)
1

$ echo "[$CLAUDE_PLUGIN_ROOT]"
[]

$ echo "$PATH" | tr ':' '\n' | grep -i plugin
(no output, exit 1)

PATH has 44 entries; zero contain "plugin". The doc pins v2.1.219; the gap reproduces on 2.1.220, which strengthens the claim rather than staling it. The owning plugin is enabled at user scope (source-control@melodic-software v0.26.2, Status enabled). An unrelated enabled plugin shipping bin/ (caveman@caveman, bin/install.js) is equally absent, so the gap is harness-wide, not plugin-specific. Both wrappers are committed 100755, present in the install cache as -rwxr-xr-x, with valid #!/usr/bin/env bash shebangs.

The documented workaround was executed, not assumed:

$ bash "/c/Users/KyleSexton/.claude/plugins/cache/melodic-software/source-control/0.26.2/bin/source-control-babysit-merge"
usage: babysit_merge.py [-h] [--allowed-owners ALLOWED_OWNERS] ...
exit 2

Exit 2 is argparse's missing-positional usage error — proof the path resolved and the wrapper executed.

Current official documentation still says what #843 claims

https://code.claude.com/docs/en/plugins-reference, File locations reference:

| 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 directory-layout diagram concurs: ├── bin/ # Plugin executables added to PATH.

On the environment variable, the same page reads: "All three are exported as environment variables to hook processes and to MCP and LSP server subprocesses." The Bash tool is not in that list, so the empty $CLAUDE_PLUGIN_ROOT conforms to the docs and is not itself the defect. The bin/-on-PATH non-delivery is.

The feature is not version-gated: anthropics/claude-code#42872 quotes the v2.1.91 changelog — "Plugins can now ship executables under bin/ and invoke them as bare commands from the Bash tool." The measured harness is 2.1.220.

Supporting doc claims also verified verbatim: the wrapper-strip list (timeout, time, nice, nohup, stdbuf, command, builtin, zsh noglobbash absent), wildcards accepted at any position including leading, and the trailing * word-boundary semantics, all from https://code.claude.com/docs/en/permissions; the four-item auto-mode drop list from https://code.claude.com/docs/en/permission-modes.

Documenting rather than fixing is justified

The repository's side is provably correct — mode bits, shebangs, cache presence, plugin enabled — and nothing in this repo can inject a directory into the Bash tool's PATH. That is harness-owned. Corroborating upstream reports of the same machinery, all closed: anthropics/claude-code#68066 (truncated shell snapshot silently dropping plugin bin/ PATH entries), #65883 (Windows-to-POSIX PATH translation in MSYS bash), #68894 (bin/ PATH injection not extended to the PowerShell tool).

Findings

1. IMPORTANT — the PR body carries superseded claims the final diff corrected. The body at lines 36-38 says Bash(*<wrapper-name> *) "would syntactically match" the bundled-path invocation. The final README.md:175-178 states the opposite: the documented form quotes the path, so the character after the wrapper name is " rather than a space, and that shape does not match. Commit afa0356ed3 fixed the README; the body was not re-synced. Body lines 29-30 also assert the interpreter-led rule "is dropped" as fact where the final README hedges (L156-159), and lines 31-35 cite a shim risk absent from the shipped text. A reviewer reading the body gets a stronger, partly-wrong account than the file delivers.

2. SUGGESTION — README.md:155 — "clearly" over-states an inference. "The documented drop categories clearly reach the wildcarded-target form (Bash(bash <path>*))." The drop list's only interpreter exemplar is Bash(python*). That bash <literal-path>* counts as a wildcarded interpreter is defensible but undocumented. The next sentence correctly hedges the fixed-path form, so the line was drawn deliberately — suggest dropping "clearly" or writing "by extension".

3. SUGGESTION — README.md:166-167 — capability claim in fact-voice. "env.PATH in user settings, which does reach the Bash tool's shell". The quoted settings sentence is generic ("subprocesses Claude Code spawns from it"); reaching the Bash tool's shell specifically was not tested. Low stakes, since the section rejects the approach anyway.

4. SUGGESTION — the root cause is attributed but not isolated. The heading scopes the gap to "Windows / Git Bash". Upstream #68066 describes a non-platform mechanism — a truncated shell snapshot dropping plugin bin/ PATH entries — that would produce identical symptoms on any OS. The section is honest that macOS/Linux is unverified, so this is under-investigation rather than over-statement, but a one-line "mechanism not isolated" would be more accurate than a platform-named heading.

5. SUGGESTION (pre-existing) — installed-plugin link traversal. plugins/claude-config/skills/audit-permission-grants/SKILL.md:18 reaches the new section via ../../../../docs/conventions/..., which escapes the plugin root. plugins-reference: "Installed plugins cannot reference files outside their directory. Paths that traverse outside the plugin root … will not work after installation." A repo reader reaches it; an agent loading the installed claude-config skill does not. Not introduced here — the same applies to criteria.md:9, claude-config/README.md:60, work-items/reference/permission-preflight.md:13, docs/PLUGIN-PHILOSOPHY.md:349.

6. SUGGESTION (pre-existing) — docs/conventions/permission-rule-hygiene/CHANGELOG.md:3-4 names the enforcing skill permission-hygiene; the actual skill is audit-permission-grants (README.md:14, and the live skill listing). Present on origin/main, but the diff edits this file, so it is adjacent Boy-Scout scope.

7. SUGGESTION — the PR reports "No linked issue" while .github/workflows/pr-issue-linkage.yml exists. The body correctly states it does not close #843; whether the gate accepts a non-closing reference is worth confirming before merge.

Placement and version-queue facts

The section sits at README.md:125-184, immediately after "The correct pattern" (ends L123) — directly qualifying the step it limits. Correct placement.

The diff touches exactly two files and bumps no plugin.json and no plugin CHANGELOG. That is correct for this repo, not an omission: scripts/check-changelog-parity.sh is scoped to plugins/<name>/.claude-plugin/plugin.json plus its sibling CHANGELOG, and --check-bump fires only if a plugin manifest changed. The convention-versioning mechanism here is the per-convention docs/conventions/*/CHANGELOG.md, which the PR bumps 1.0 to 1.1. Matching precedent: 8cd71e688f touched only a convention README and CHANGELOG with no plugin.json. The three recent commits that did bump plugin.json each also touched files under plugins/; this one does not.

So #1277 is outside the plugins/source-control version queue entirely and does not need to wait behind #1264.

Over- or under-statement

Neither, materially. The section names the platform, admits macOS/Linux is unverified, explicitly warns against reading the heading as clearance, labels the leading-wildcard candidate "untested rather than rejected", and volunteers the security downside of the candidate it floats. The only calibration slips are findings 2-4.

Could not verify

  • Cross-platform behavior on macOS/Linux — only a Windows/Git-Bash machine available. The doc declares this unverified itself, so it is not a gap in the doc.
  • Whether a leading-wildcard rule survives auto mode — undocumented upstream, not testable read-only. The doc declares it unverified.
  • Whether an upstream functional report exists for this exact non-delivery. Three related upstream issues were found, all closed, none matching.
  • Shell-snapshot inspection to test the #68066 hypothesis directly — abandoned when the Bash tool began timing out under concurrent-agent load; non-essential to the verdict.

@kyle-sexton

Copy link
Copy Markdown
Contributor Author

Review-thread triage

Thread state re-derived from reviewThreads on the current head. 3 threads, all unresolved; 2 are
superseded by the current head, 1 is live.
Each was re-read against the file as it now stands.

# Thread Classification Basis
1 README.md:155 — "Distinguish fixed-script rules from dropped broad rules" VALID, already addressed Superseded by the current head
2 README.md:169 — "Match the quoted bundled-path invocation" VALID, already addressed Superseded by the current head
3 README.md:152 — "Update the audit for the Git Bash fallback" VALID, live Filed separately — see below

1 — superseded

The categorical claim the reviewer objected to is gone. The current text distinguishes the two
shapes explicitly and matches audit-permission-grants/reference/criteria.md:43-49:

The documented drop categories clearly reach the wildcarded-target form (Bash(bash <path>*));
whether they reach a fixed-path form (Bash(bash <fixed-path>:*)) is not stated, so that shape is
an anti-pattern on convention grounds rather than a confirmed drop.

2 — superseded

The current text states the mismatch the reviewer identified, as its own bullet:

The documented bundled-path form quotes the path, so the character following the wrapper name is a
closing quote, not a space — a candidate shaped Bash(*<wrapper-name> *) does not match it, and
fails before the auto-mode question is even reached.

3 — live, and deliberately out of scope for this PR

The finding is correct and unaddressed: permission-rule-check.sh still classifies
Bash(bash ${CLAUDE_PLUGIN_ROOT}/...:*) as P1, and audit-permission-grants/SKILL.md:61-65
unconditionally recommends replacing it with a bare command on PATH — the substitute this PR
establishes does not exist on the measured platform. So the audit hands affected users a remediation
that cannot work.

It is not fixed here on purpose. This PR touches no plugin.json; adding the claude-config fix
would pull it into the version queue for a docs-only change. Tracked as its own issue instead.


Posted as a PR-level comment rather than as thread replies: replying in-thread requires a GraphQL
mutation that this session's operating rules exclude.

@kyle-sexton

Copy link
Copy Markdown
Contributor Author

Thread 3 (README.md:152, the audit contradiction) is now tracked as #1407, with the P1 classification re-verified against the released ERE and the three unconditional bare-name-on-PATH remediation sites cited. It stays out of this PR so this docs-only change stays out of the version queue.

@kyle-sexton
kyle-sexton merged commit 4c7c21a into main Jul 25, 2026
28 checks passed
@kyle-sexton
kyle-sexton deleted the docs/843-plugin-bin-path-gap branch July 25, 2026 22:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

source-control:babysit-prs: plugin bin/ not on Bash-tool PATH — guarded wrapper bare names unresolvable (blocks narrow allow-rule match)

1 participant