docs(permission-rule-hygiene): record the undelivered plugin bin/-on-PATH capability#1277
Conversation
…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 finished @kyle-sexton's task in 1m 34s —— View job PR Review
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 correctnessAll claims in the new section are either directly cited or logically derived from cited sources:
Platform scopingThe 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 Section placement and CHANGELOG
CLAUDE.md alignmentThe fresh-docs mandate requires every claim to come from a fetched page. The section cites SummaryTwo advisory suggestions:
Neither is a blocker. The core record — measured behavior, scope, consequences, workaround — is accurate, cited, and actionable. |
There was a problem hiding this comment.
💡 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".
…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 finished @kyle-sexton's task in 2m 42s —— View job PR Review
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 validThe Codex reviewer flagged
This contradicts
So the criteria document flags The second commit ( 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. Prior advisory 1 — still openThe 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 expandedThe 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 What this PR gets right
|
There was a problem hiding this comment.
💡 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".
…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 finished @kyle-sexton's task in 2m 15s —— View job PR Review
Verdict: Approve. The latest commit ( What
|
Review round — classification (commit
|
| # | 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).
Independent verification — VERDICT: PASS-WITH-FINDINGSAudited 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)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 ( The documented workaround was executed, not assumed: Exit 2 is argparse's missing-positional usage error — proof the path resolved and the wrapper executed. Current official documentation still says what #843 claimshttps://code.claude.com/docs/en/plugins-reference, File locations reference:
The directory-layout diagram concurs: 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 The feature is not version-gated: Supporting doc claims also verified verbatim: the wrapper-strip list ( Documenting rather than fixing is justifiedThe 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: Findings1. IMPORTANT — the PR body carries superseded claims the final diff corrected. The body at lines 36-38 says 2. SUGGESTION — 3. SUGGESTION — 4. SUGGESTION — the root cause is attributed but not isolated. The heading scopes the gap to "Windows / Git Bash". Upstream 5. SUGGESTION (pre-existing) — installed-plugin link traversal. 6. SUGGESTION (pre-existing) — 7. SUGGESTION — the PR reports "No linked issue" while Placement and version-queue factsThe section sits at The diff touches exactly two files and bumps no So #1277 is outside the Over- or under-statementNeither, 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
|
Review-thread triageThread state re-derived from
1 — supersededThe categorical claim the reviewer objected to is gone. The current text distinguishes the two
2 — supersededThe current text states the mismatch the reviewer identified, as its own bullet:
3 — live, and deliberately out of scope for this PRThe finding is correct and unaddressed: It is not fixed here on purpose. This PR touches no Posted as a PR-level comment rather than as thread replies: replying in-thread requires a GraphQL |
|
Thread 3 ( |
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'sbin/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 doesnot close #843 — the capability itself is upstream.
Fix
docs/conventions/permission-rule-hygiene/README.mdgains a Known gap section after the correctpattern, recording:
which <wrapper>exits 1 and no plugin directory of any kind appears onPATH, on Windows 11 / Git Bash, Claude Code v2.1.219, with the owning plugin installed at userscope and reported
enabled.plugin that also ships a
bin/is equally absent fromPATH. The files are committed100755,present in the install cache, with correct shebangs, and the feature predates the measured harness.
${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 —
bashis not one of the wrappers Claude Code stripsbefore matching, so a rule for a
bash <path> …command has to namebash, making itinterpreter-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>:*)) isnot stated upstream, so that shape is an anti-pattern on convention grounds rather than a
confirmed drop.
~/.local/binshim pins aversion-numbered install path that changes on every plugin update; an
env.PATHentry in usersettings does reach the Bash tool's shell but carries the same version-pinned rot, plus the hazard
of overriding
PATHwholesale.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
Agentrules, and is silent on a leading wildcard in the command position.CHANGELOG.mdfor the convention records this as 1.1. The convention versions independently of anyplugin, so this carries no
plugin.jsonbump.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/PATHprobe, Git Bash, this machine:Harness-wide scope: the same probe against the
cavemanplugin'sbin/(a differentmarketplace, unrelated author) is equally absent, so this is not a
source-controlpackagingdefect.
Plugin state:
claude plugin listreportssource-control@melodic-softwarev0.26.0, scopeuser,
✔ enabled;bin/is present in the install cache;git ls-files -sshows both wrappers atmode
100755.Harness version:
claude --version→2.1.219, well past the release that introducedbin/-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
whichfailurefrom 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.PATHlever confirmed, then rejected on durability, not capability: a nestedclaude -p --settings '{"env":{"ZZ_PATH_PROBE":"REACHED"}}'run echoed[REACHED]from its Bashtool, so settings
envdoes reach that shell. It is recorded as a non-substitute because of theversion-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 pluginsettings.json;${CLAUDE_PLUGIN_ROOT}exported to hook,MCP, and LSP subprocesses only),
permissions (the stripped-wrapper
list, which does not include
bash), andpermission-modes
(the auto-mode drop list).
Gates:
markdownlint-cli2 --config .markdownlint-cli2.jsoncover both changed files —Summary: 0 error(s).typos --config _typos.tomlover the convention directory — exit 0.Independent re-verification (fresh context, this branch's pushed head
afa0356ed3, rationalewithheld): 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"reachesbabysit_merge.py'sargparse 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#42872quotes the v2.1.91 changelog introducing the feature. Full report in the PR comments.
Related
consequences, but delivering
bin/-on-PATHis an upstream Claude Code capability that cannot beimplemented here.
${CLAUDE_PLUGIN_ROOT}/bin/path-form workaround this section points atas the invocation that works today.