Skip to content

feat(claude-permissions): allow gh pr review in the fleet floor - #228

Merged
kyle-sexton merged 2 commits into
mainfrom
feat/claude-permissions-allow-gh-pr-review
Jul 21, 2026
Merged

feat(claude-permissions): allow gh pr review in the fleet floor#228
kyle-sexton merged 2 commits into
mainfrom
feat/claude-permissions-allow-gh-pr-review

Conversation

@kyle-sexton

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

Copy link
Copy Markdown
Contributor

What

Adds gh pr review comment-form spellings to the claude-permissions allow floor, alphabetically between gh pr ready and gh pr view:

  • Bash(gh pr review * --comment *)
  • Bash(gh pr review * --comment)
  • Bash(gh pr review --comment *)

--approve and --request-changes forms are deliberately NOT floor-allowed — they can satisfy or block required-review gates, so they stay prompted per session.

Why

Component change discipline requires observed-usage evidence for allow additions: recurring real-session need to submit PR review comments from agent sessions (recorded in the plugin-batch session handoff, 2026-07-21). Fits the existing "PR and issue CRUD" allow posture; gh pr merge --auto remains denied.

Distribution

Reaches the dotfiles consumer (.chezmoidata/claude-permissions.json) through the ordinary sync PR; the modify-template union then composes it into ~/.claude/settings.json.

Related

No linked issue. Component introduced in #210; observed-usage evidence recorded in the plugin-batch session handoff (claude-code-plugins .work/handoffs/20260721T053034Z-handoff-plugin-batch-complete.md).

🤖 Generated with Claude Code

https://claude.ai/code/session_01U9zbRxtRuTx1WsQavFc2Gi

Observed-usage evidence per the component's change discipline: recurring
real-session need to submit PR reviews from agent sessions (review verb
only; merge remains prompted and `gh pr merge --auto` remains denied).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U9zbRxtRuTx1WsQavFc2Gi
@claude

claude Bot commented Jul 21, 2026

Copy link
Copy Markdown

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


Code Review

  • Gather context and understand the change
  • Read the changed file and surrounding entries
  • Check REVIEW.md for project-specific review criteria
  • Verify alphabetical ordering claim
  • Assess security / allow-posture implications
  • Post review findings

File reviewed: components/claude-permissions/claude-permissions.json

Change: + "Bash(gh pr review *)",


Summary

Clean, minimal change. The addition is correct, well-placed, and fits the established allow posture. No blocking findings under the REVIEW.md criteria (no secrets, no injection surface, no tenant data, no atomicity concern — this is pure config).


Findings

Alphabetical placement — correct. The sequence at lines 18–20:

"Bash(gh pr ready *)",
"Bash(gh pr review *)",   ← new
"Bash(gh pr view *)",

readyreviewview is lexicographically ordered. ✅

Change discipline — satisfied. The component README requires "observed-usage evidence (recurring prompt patterns from real sessions)" for allow additions. The PR body supplies this via the plugin-batch session handoff reference (2026-07-21). ✅

Allow posture consistency — the existing allow list already permits gh pr comment *, gh pr create *, and gh pr ready *. Adding gh pr review * extends "PR and issue CRUD" coverage consistently and without widening the blast radius relative to gh pr create. ✅

gh pr merge remains gated — confirmed. gh pr merge --auto is in the deny list (lines 98–99); bare gh pr merge is absent from both allow and deny, so it continues to prompt. ✅

No PowerShell counterpart — consistent with existing pattern. Write/mutating gh operations (comment, create, ready) are Bash-only in the allow list; read-only ones (checks, diff, list, view) are mirrored to PowerShell. Not adding PowerShell(gh pr review *) follows that convention. ✅


🟡 Nit — wildcard covers --approve

The entry Bash(gh pr review *) matches all subcommand forms, including gh pr review --approve <number>, which allows an agent session to autonomously approve a PR. The PR body says "review verb only" but the wildcard is broader than commenting — it includes approval. This appears intentional (and the component README is explicit that the deny list cannot enumerate every flag permutation), but it's worth a deliberate acknowledgment that autonomous approval is in scope.

If the intent were to restrict to --comment only, the entry would need to be narrowed. If autonomous approval is acceptable — consistent with the existing posture that already allows gh pr create * and gh pr ready * — no change is needed.


Overall: LGTM with the nit above acknowledged. The change is correct, properly evidenced, and in scope.

@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: 3e91d02a3e

ℹ️ 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 components/claude-permissions/claude-permissions.json Outdated
…iews

The bare wildcard also covered --approve and --request-changes, which can
satisfy or block required-review gates unattended. Only --comment spellings
are floor-allowed; approval and request-changes forms stay prompted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U9zbRxtRuTx1WsQavFc2Gi
@claude

claude Bot commented Jul 21, 2026

Copy link
Copy Markdown

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


Code Review

  • Gather context and understand the change
  • Read the changed file at HEAD (2455888)
  • Verify the narrowed permission entries
  • Check REVIEW.md criteria
  • Post review findings

File reviewed: components/claude-permissions/claude-permissions.json

Change (commit 2455888): Replaced the original broad "Bash(gh pr review *)" with three narrowed entries:

"Bash(gh pr review * --comment *)",
"Bash(gh pr review * --comment)",
"Bash(gh pr review --comment *)",

Summary

The narrowing correctly addresses both the Codex P1 finding and the nit raised in the previous Claude review. The three patterns collectively cover the realistic --comment-only agent use cases while leaving --approve and --request-changes unprompted. No blocking REVIEW.md criteria apply (no secrets, no injection surface, no tenant data, no atomicity concern).


Findings

Wildcard bypass risk — mitigated by CLI mutual-exclusivity — The pattern "Bash(gh pr review * --comment *)" could theoretically match a command like gh pr review 123 --approve --comment foo (first * = 123 --approve). However, gh pr review treats --approve, --comment, and --request-changes as mutually exclusive; passing two of them produces a CLI error. The bypass path is blocked at the tool layer, not the pattern layer. No change needed; worth knowing the defense is CLI-enforced rather than pattern-enforced. ✅

Alphabetical ordering — Correct per ASCII value. * (0x2A) sorts before - (0x2D), so the two "gh pr review * ..." entries precede "gh pr review --comment *". Within the *-prefixed pair, --comment *) sorts before --comment) because space (0x20) < ) (0x29). The final sequence at lines 18–22 is:

"Bash(gh pr review * --comment *)",
"Bash(gh pr review * --comment)",
"Bash(gh pr review --comment *)",

All lexicographically ordered. ✅

Coverage of realistic agent forms — The three entries cover: (1) PR number with body args, (2) PR number without inline body (rare interactive form), and (3) current-branch inference with body args. The --body/-b flag needed to pass review text in non-interactive sessions is captured by the trailing *. ✅

--approve and --request-changes remain prompted — Confirmed. Neither appears in the allow list, and neither is in the deny list, so both continue to prompt. ✅

No PowerShell mirror — Consistent with the established pattern (write/mutating operations are Bash-only in the allow list). ✅

No REVIEW.md blocking criteria — No authorization bypass, no tenant-scoped data path, no secrets in tracked source, no injection surface, no atomicity concern. ✅


🟡 Nit — "Bash(gh pr review --comment)" is absent

The current-branch form with no inline body (gh pr review --comment — drops into an interactive editor) has no matching entry. Agents operating non-interactively will always supply a body via --body/-b so this gap is inconsequential in practice; it's a completeness observation only.


Overall: LGTM. The narrowing is correct, the wildcard bypass is closed at the CLI layer, and the change satisfies the component's change-discipline requirement. Good fix.

@kyle-sexton
kyle-sexton merged commit 59e0534 into main Jul 21, 2026
43 of 44 checks passed
@kyle-sexton
kyle-sexton deleted the feat/claude-permissions-allow-gh-pr-review branch July 21, 2026 06:03

@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: 2455888a34

ℹ️ 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".

"Bash(gh pr diff *)",
"Bash(gh pr list *)",
"Bash(gh pr ready *)",
"Bash(gh pr review * --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.

P1 Badge Require --comment to be the parsed review action

Fresh evidence after the wildcard was narrowed: this glob still auto-allows commands such as gh pr review 123 --body --comment --approve, where --comment is just the string value for GitHub CLI's --body <string> flag and the actual review action is --approve (or similarly --request-changes). The gh pr review implementation then sets ReviewApprove when flagApprove is true, so an unattended session with review permission can still approve or block a PR while matching this line's textual --comment pattern; constrain the allow rule so --comment cannot be consumed as another option's value.

Useful? React with 👍 / 👎.

kyle-sexton added a commit that referenced this pull request Jul 26, 2026
…ins (#267)

## What

Re-derives the `claude-permissions` allow floor against Claude Code auto
mode's
built-in coverage (fleet default is now `permissions.defaultMode:
"auto"`;
migration tracked in melodic-software/dotfiles#309, this PR is its P1b
phase).
Allow floor 90 → 81; deny grows by 14 pull-spelling rows (`--no-verify`
and forced-fetch families mirrored onto `git pull`, both shells — the
review round that also dropped the rerun grant); the component carries
no ask array.

An earlier shape of this PR trimmed 45 rows on the premise "auto mode
covers
read-only commands promptless." That premise was then MEASURED and found
false
for everything except read-only git (empirical record: the
operator-local
migration file `.work/auto-mode-defaults/RESEARCH.md`, "Addendum —
built-in
read-only set, MEASURED" — not repo-reachable, so the decisive figures
are
inlined here; `claude -p --setting-sources "" --permission-mode
default`,
v2.1.219): **9/9 git inspection spellings ran promptless** (`status`,
`diff`,
`log`, `show`, `branch`, `branch --list`, `ls-files`, `merge-base`,
`rev-parse`); **16/16 non-git commands were denied** — every `gh` read
verb, every third-party linter, even
bare `--version`. Membership is command identity, not flag safety, and
it is
tool-agnostic (git ran promptless through `PowerShell()` too). The trim
was
re-derived accordingly:

- **TRIM 16** — read-only *git* inspection only (10 `Bash()` + 6
`PowerShell()` spellings). The built-in read-only command set runs these
  promptless in every mode, so floor entries for them are dead weight
  everywhere, not just under auto.
- **RESTORE 29** — the read-only `gh` verbs, third-party linters, and
`claude plugin` inspection rows the earlier shape wrongly trimmed.
Measured
denied without them; they are load-bearing in every non-auto lane
(workflow
subagents always run `acceptEdits`; headless `-p`/SDK runs fail with
nobody
  to answer a prompt).
- **REPLACE 2 dead rows with 4 live spellings** — the bare-wrapper rows
  `Bash(source-control-babysit-merge *)` /
`Bash(source-control-babysit-resolve-thread *)` match nothing today: the
plugin `bin/` directory is not on the shell's PATH and the skill invokes
  each wrapper as `bash "${CLAUDE_PLUGIN_ROOT}/bin/<wrapper>"`
  (claude-code-plugins `3fc72d351c`). They are dropped, and the wrappers
  instead carry the same interim path-form convention as the five plugin
  scripts already on the floor — quoted and unquoted
  `Bash(bash …${CLAUDE_PLUGIN_ROOT}/bin/<wrapper>…*)` spellings, 4 rows.
  Bare-name rows return when claude-code-plugins#843 makes the bare name
  resolve.
- **DO NOT PROMOTE the 4 `pytest` rows** the earlier shape added. A test
runner executes whatever test files are on disk, and a session that can
  write files plus a blanket `pytest` grant compose into a general
  code-execution grant wearing a narrow name — the same refusal
melodic-software/dotfiles#315's git entry records three times. Test runs
  are judged per session.
- **PROMOTE 6** — additions with observed-usage evidence or a reviewed
unattended-lane need, per the component's change discipline: `git pull`
(bare + starred) and four gh write verbs (rationale below). No
PowerShell
parity rows: the PS-mirror convention covers read-only inspection only,
and
  write verbs have never carried mirrors.

### Promote — issue/PR metadata verbs and CI re-run

- `gh issue close *`, `gh issue reopen *`, `gh pr edit *` — the same
class as
  floor incumbents already present (`gh issue comment/create/edit`,
`gh pr comment/create/ready`); excluding them was an arbitrary gap in an
  otherwise-consistent issue/PR metadata-write posture.

### Considered, not promoted (decision record)

- `Bash(gh api graphql *)`, `Bash(gh api orgs/*)`, `Bash(gh api
repos/*)` —
empirical evidence in melodic-software/claude-code-plugins#1235 (gap 2)
shows prefix allow rules of exactly this shape did NOT short-circuit the
classifier's category safety layer for external writes. They also widen
the
granted surface in non-auto sessions, which is precisely the posture the
  floor's rows exist to define. Kept machine-local.
- `Bash(gh workflow run *)` — dispatches arbitrary workflows with
operator-controlled inputs; a CI-execution trigger is a different risk
tier
  from issue/PR metadata edits, and the
melodic-software/claude-code-plugins#697 precedent keeps
high-blast-radius
  write verbs (merge) off the floor. Kept machine-local.
- `Edit(//**/github.com/melodic-software/**)` — encodes this machine's
  checkout layout; a fleet-distributed floor must not carry a machine's
  directory layout. Kept machine-local.

## Scope limit under auto mode

These grants are deterministic only outside auto mode's
`classifyAllShell`.
That key is NOT yet set fleet-wide: G3a split it out of
melodic-software/dotfiles#315 into its own gated follow-up draft PR in
dotfiles, which lands only after the operator's prose-grant test
(dotfiles#309 P3 test 3) passes. Until then the
narrow pre-classifier short-circuit these rows provide still operates in
auto-mode sessions for matching spellings. Once the key lands, every
shell
allow rule is suspended in auto-mode sessions and this floor is the
fallback
posture for non-auto lanes — which the measurement above shows are
exactly the
lanes that need it.

`deny` is unaffected in every mode and every regime — `classifyAllShell`
suspends allow rules only; the 262 deny entries stay pre-classifier and
non-overridable, which is exactly why G1 keeps all of them.

## Why

- The floor predates auto mode: it was built (#210, #223, #228; lineage
melodic-software/claude-code-plugins#695) to pre-approve safe commands
so
  unattended loops never prompt. Auto mode provides that natively for
read-only git only — the measured set — so exactly those entries no
longer
  pay for their maintenance surface.
- Merge-verb policy is unchanged: `gh pr merge` stays out of the allow
floor
  and `gh pr merge --auto` stays denied, per
melodic-software/claude-code-plugins#697; babysit merges keep going
through
  the guarded gate wrapper.
- Deny/ask keep-all is a locked decision (dotfiles#309 G1): explicit
deny is
pre-classifier and non-overridable, while built-in coverage is
consent-gated
soft_deny — trimming deny would downgrade hard blocks to consent gates.

## Per-entry disposition (reconciles to 90)

Baseline 90 = TRIM 16 + DROP 2 + KEEP 72; final 81 = KEEP 72 + PROMOTE 5
+
REPLACE 4 (the wrappers' path-form spellings). A review round later
dropped the
sixth PROMOTE candidate (`gh run rerun *` — a bare run id names any
accessible
run, so no floor glob holds it to merged workflow code; judged per
session
until a guarded wrapper exists).

<details>
<summary>TRIM — 16 entries (read-only git, built-in-covered,
measured)</summary>

| Entry |
| --- |
| `Bash(git branch --list *)` |
| `Bash(git branch)` |
| `Bash(git diff *)` |
| `Bash(git log *)` |
| `Bash(git ls-files *)` |
| `Bash(git merge-base *)` |
| `Bash(git rev-parse *)` |
| `Bash(git show *)` |
| `Bash(git status *)` |
| `Bash(git status)` |
| `PowerShell(git diff *)` |
| `PowerShell(git log *)` |
| `PowerShell(git merge-base *)` |
| `PowerShell(git rev-parse *)` |
| `PowerShell(git show *)` |
| `PowerShell(git status *)` |

</details>

<details>
<summary>DROP 2 / REPLACE 4 — bare wrappers out, path-form spellings
in</summary>

Dropped (match nothing until ccp#843):

| Entry |
| --- |
| `Bash(source-control-babysit-merge *)` |
| `Bash(source-control-babysit-resolve-thread *)` |

Added in their place (the real invocation shape, parity with the five
script rows):

| Entry |
| --- |
| `Bash(bash "${CLAUDE_PLUGIN_ROOT}/bin/source-control-babysit-merge"*)`
|
| `Bash(bash ${CLAUDE_PLUGIN_ROOT}/bin/source-control-babysit-merge*)` |
| `Bash(bash
"${CLAUDE_PLUGIN_ROOT}/bin/source-control-babysit-resolve-thread"*)` |
| `Bash(bash
${CLAUDE_PLUGIN_ROOT}/bin/source-control-babysit-resolve-thread*)` |

</details>

<details>
<summary>KEEP — 72 entries</summary>

Everything else in the baseline: the non-destructive working verbs, the
gh
write incumbents, the RESTORED read-only `gh`/linter/`claude plugin`
rows
(measured load-bearing), the 10 interim `${CLAUDE_PLUGIN_ROOT}`
script-path
rules (README carries the interim note; end state is bare wrappers on
the
plugin `bin/` PATH, trigger claude-code-plugins#843), and the PowerShell
read-only mirrors outside the built-in git set.

</details>

<details>
<summary>PROMOTE — 6 entries</summary>

| Entry | Evidence |
| --- | --- |
| `Bash(gh issue close *)` | metadata-write parity with floor incumbents
|
| `Bash(gh issue reopen *)` | metadata-write parity with floor
incumbents |
| `Bash(gh pr edit *)` | metadata-write parity with floor incumbents |
| `Bash(git pull *)` | operator runtime keeper (dotfiles#309 P2 keep
set) |
| `Bash(git pull)` | operator runtime keeper (dotfiles#309 P2 keep set)
|

</details>

## Status

DRAFT — stays draft pending operator review of this amended body; merges
AFTER
melodic-software/dotfiles#315 (merging this first would trigger the
standards→dotfiles sync bot to rewrite
`.chezmoidata/claude-permissions.json`
mid-flight under #315).

## Related

No linked issue. This is the P1b phase of a cross-repo migration; it
closes no
issue in this repository.

- melodic-software/dotfiles#309 — auto-mode migration (P1b phase)
- melodic-software/dotfiles#315 — host-side auto-mode hardening
(merge-order dependency)
- melodic-software/claude-code-plugins#697 — merge-verb /
classifier-tuning policy
- melodic-software/claude-code-plugins#843 — bare-wrapper PATH end state
- melodic-software/claude-code-plugins#695 — component lineage
(introduced in #210)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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.

1 participant