Skip to content

feat(claude-permissions): fleet permission floor component, distributed to dotfiles - #210

Merged
kyle-sexton merged 8 commits into
mainfrom
feat/claude-permission-floor
Jul 20, 2026
Merged

feat(claude-permissions): fleet permission floor component, distributed to dotfiles#210
kyle-sexton merged 8 commits into
mainfrom
feat/claude-permission-floor

Conversation

@kyle-sexton

Copy link
Copy Markdown
Contributor

Summary

New claude-permissions component — the fleet's reviewed Claude Code permission floor (Boris's step-2 guardrail verbatim: "Pre-approve common safe bash and MCP commands in settings.json") — distributed as DATA to the dotfiles consumer, which owns runtime composition into ~/.claude/settings.json via its existing modify-template (the runner-policy consumer-handoff pattern; the sync engine stays byte-exact, no merge primitive touched).

  • deny (170): strictest observed union of the fleet's two substantive deny surfaces (medley project settings + user-global), both Bash()/PowerShell() spellings, bare + starred forms: force-push/hard-reset/clean/discard family, forced branch deletion, --no-verify, gh api DELETE surface, hook-disable env prefixes, secret-material Read() patterns.
  • allow (60): read-only git/gh inspection, routine non-destructive working verbs (the unattended-lane set from claude-code-plugins#495's observed gap), fleet lint tooling, and PowerShell read-parity — evidence-backed: 2026-07-20 transcript mining (548 real permission prompts over two weeks) found zero PowerShell() allow rules, so every PowerShell invocation prompted by construction. Merge verbs deliberately excluded (operator-policy, not safe-verb).
  • Manifest: component added; melodic-software/dotfiles adopts as managed.chezmoidata/claude-permissions.json. Machine-layer (managed policy settings via provisioning) is deferred with a recorded trigger in the component README.
  • sync-manifest.sh validate + schema validation + full engine test suite (139 PASS) green.

Consumer-side composition (dotfiles modify-template unioning these rules into the live settings) lands as the follow-up dotfiles PR; claude-code-plugins#495 (permission preflight) and #697 (classifier tuning) consume this component.

Related

  • claude-code-plugins#695 (this component is that item's distribution mechanism — closed when the dotfiles composition PR lands)
  • claude-code-plugins#495, claude-code-plugins#697 (consumers)

No linked issue in this repository.

🤖 Generated with Claude Code

…ed to dotfiles

One reviewed allow/deny set for Claude Code agent sessions (Boris step-2
guardrail: pre-approve common safe commands), shipped as data and
composed by the consumer, never merged by the sync engine (runner-policy
handoff pattern). deny = strictest observed union of the fleet's two
substantive deny surfaces (170 rules: destructive git verbs in Bash +
PowerShell spellings, gh api DELETE surface, hook-disable prefixes,
secret-material reads). allow = 60 safe-everywhere rules: read-only
inspection, routine non-destructive working verbs, lint tooling, plus
PowerShell read-parity — the one additive gap 2026-07-20 transcript
mining surfaced (all 47 PowerShell prompts fired because zero
PowerShell() allow rules existed). Data nests under one unique
claudePermissions key for the consumer's shared template-data namespace.
Machine-layer (managed policy) placement deferred with trigger in the
component README.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@claude

claude Bot commented Jul 20, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 3m 26s —— View job


Code Review — feat(claude-permissions)

  • Gather context (PR diff, changed files)
  • Read REVIEW.md for review criteria
  • Review components/claude-permissions/claude-permissions.json
  • Review components/claude-permissions/README.md
  • Review distribution/sync-manifest.yml changes
  • Synthesize findings and post review

The component structure, composition model, manifest entry, and README narrative are all sound. The runner-policy handoff pattern (standards owns the data, consumer owns the merge) is applied correctly and the sync-manifest.yml entry maps cleanly to .chezmoidata/claude-permissions.json with no requires: dependency needed for a pure-data component. Two correctness issues in the deny list need attention before this can be relied on as a security floor.


🔴 Important — git branch -D missing from Bash deny

The PowerShell deny list explicitly covers the -D shorthand for force-branch-deletion:

"PowerShell(git branch -D *)",
"PowerShell(git branch -D)",

The Bash deny list covers all the long-form permutations (--delete --force, --delete -f, -d --force, -df, -fd, etc.) but "Bash(git branch -D *)" and "Bash(git branch -D)" are absent. -D is the most common real-session form — it is what Claude defaults to when force-deleting a branch, and it is what transcript evidence will surface most often. An agent running in Bash shell can execute git branch -D feat/some-branch today and the floor does not block it.

Bash deny list, after line 108 — add:

"Bash(git branch -D *)",
"Bash(git branch -D)",

Fix this →


🔴 Important — dotnet user-secrets:* deny rule is a no-op

Line 70:

"Bash(dotnet user-secrets:*)",

The actual .NET CLI invocation is dotnet user-secrets set MyKey MyValue (space-separated subcommand, no colon). The colon syntax (dotnet user-secrets:*) is not a valid dotnet CLI form — it resembles MSBuild target notation but user-secrets is a dotnet global tool invoked without a colon. This rule will never match dotnet user-secrets set|list|remove|clear in real sessions, leaving those commands to prompt rather than be denied.

If the intent is to block user-secrets manipulation entirely, the pattern should be:

"Bash(dotnet user-secrets *)",

If this was meant to protect against dotnet-user-secrets (hyphenated tool invocation) or another specific pattern, the intent should be documented.

Fix this →


🟡 Nit — Bash deny has no git -C /path prefix variants for destructive operations

The PowerShell deny includes git * prefix forms (lines 147–159) specifically to catch invocations like git -C /some/repo clean --force:

"PowerShell(git * clean --force)",
"PowerShell(git * push --force)",
"PowerShell(git * reset --hard)",
...

The Bash deny has no equivalent. git -C /some/path clean --force in a Bash shell does not match "Bash(git clean --force *)" because the command starts with git -C. Whether this is a coverage gap worth closing or an acceptable delta depends on how frequently agents use the -C flag in Bash sessions, but the coverage asymmetry between platforms is worth noting.

🟡 Nit — Read(**/.claude.json) targets a non-standard filename

Line 212: .claude.json is not a current Claude Code configuration filename (Claude Code uses .claude/settings.json and .claude/settings.local.json, both of which are already covered). If this is defensive against a third-party tool or a historical filename, a comment in the README's deny description would clarify the intent.

🟡 Nit — PowerShell allow is read-only; no commit/push/add for Windows agents

The PowerShell allow list covers git/gh inspection only — no git commit, git push, git add, git checkout, or git switch. On Windows, an agent session in PowerShell will prompt for every write operation even after this floor is applied. The README mentions "PowerShell read-parity" in passing but doesn't surface the implication that Windows agents operate in a fundamentally more restricted posture. If intentional, a direct statement of the platform asymmetry would prevent future contributors from assuming parity.


Summary: The composition model and manifest wiring are correct. The two 🔴 findings are genuine gaps in the security floor this component is designed to provide — the -D shorthand gap in particular is the most likely real-session bypass. The three nits are minor clarifications. The allow set and the bulk of the deny surface look well-constructed.

@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: 4fa0e04923

ℹ️ 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
Comment thread components/claude-permissions/claude-permissions.json
Review gaps: Bash(git push *) also pre-approved --delete/-d and
--force-with-lease spellings, and Bash(git add *) pre-approved -f/--force
staging of ignored files (secret-material .env class). All four families
now denied in both shell spellings, bare-flag and mid-command forms; deny
wins over the broad allows.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@claude

claude Bot commented Jul 20, 2026

Copy link
Copy Markdown

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


Code Review — feat(claude-permissions) (updated for 0c3856e)

  • Gather context (PR diff, changed files)
  • Check what changed in the latest commit (0c3856e)
  • Verify previous 🔴 findings from last review are addressed
  • Review new deny rules added in 0c3856e
  • Synthesize and post updated review

What 0c3856e fixed

The follow-up commit correctly addresses both Codex P1 findings:

  • git add -f/--force: All four positional forms denied in both shell flavors (git add -f *, git add --force *, git add * -f *, git add * --force *) — the forced-staging-of-ignored-files bypass is closed.
  • git push --delete/-d and --force-with-lease: All expected variants (bare-flag, mid-command, both shells) are now denied. The --force-with-lease* glob correctly catches both the bare flag and the =<refname> form.

🔴 Open — Bash(git branch -D *) still absent

This was the first 🔴 in the prior review and remains unaddressed. The PowerShell block has:

"PowerShell(git branch -D *)",   // line 178
"PowerShell(git branch -D)",     // line 179

The Bash block has every long-form permutation (--delete --force, --delete -f, -d --force, -df, -fd, etc.) but no -D entry. The Bash deny runs lines 103–112:

"Bash(git branch --delete --force *)",
"Bash(git branch --delete -f *)",
"Bash(git branch --force --delete *)",
"Bash(git branch --force -d *)",
"Bash(git branch -d --force *)",
"Bash(git branch -d -f *)",
"Bash(git branch -df *)",
"Bash(git branch -f --delete *)",
"Bash(git branch -f -d *)",
"Bash(git branch -fd *)",

git branch -D feat/some-branch in a Bash session is not blocked. This is the most common real-session form. Insert after line 109 ("Bash(git branch -df *)") to maintain order:

"Bash(git branch -D *)",
"Bash(git branch -D)",

Fix this →


🔴 Open — Bash(dotnet user-secrets:*) is still a no-op

Line 70 is unchanged:

"Bash(dotnet user-secrets:*)",

The colon is not part of the dotnet CLI — dotnet user-secrets set, list, remove, clear are all space-separated. This pattern never matches any real invocation. Replace with:

"Bash(dotnet user-secrets *)",

Fix this →


🟡 New observation — git push origin :branch (colon-prefix delete) not covered

The new rules correctly block git push --delete <remote> <ref> and git push -d. However, git push origin :branch-name is the older colon-prefix syntax for deleting a remote ref, and it is absent from both shell deny lists. A session can delete a remote branch today using this form in both Bash and PowerShell. This is a pre-existing gap (not introduced by 0c3856e) but surfaced now that the --delete/-d family is fully covered. Adding "Bash(git push * :*)" and the PowerShell equivalent would close it.


🟡 Nits (carried from prior review, still open)

  • git -C /path prefix for Bash: PowerShell deny covers git * clean --force, git * push --force, etc. (lines 157–169). Bash has no equivalents. An agent using git -C /repo clean --force in Bash bypasses the deny floor.
  • Read(**/.claude.json) (line 232): .claude.json is not a current Claude Code filename; intent should be documented in README if this is defensive against a historical or third-party pattern.
  • PowerShell write-op posture: The README mentions "PowerShell read-parity" but Windows agents will prompt for every git commit, git push, git add, git checkout, and git switch. Worth stating the asymmetry explicitly.

Summary: 0c3856e correctly closes the two Codex P1 gaps. The two 🔴 findings from the prior review — Bash(git branch -D *) absence and the dotnet user-secrets:* no-op — are still present and block this from being a reliable security floor. The colon-prefix push delete is a new minor gap worth closing alongside the others.

@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: 0c3856e3ca

ℹ️ 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
Comment thread components/claude-permissions/claude-permissions.json
…no-verify spellings

Review gaps: git checkout -B / git switch -C reset an existing branch
under the broad checkout/switch allows, and git commit -n is the short
--no-verify spelling the long-form deny missed. All denied in both shell
spellings; bare and mid-command forms.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@claude

claude Bot commented Jul 20, 2026

Copy link
Copy Markdown

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


Code Review — feat(claude-permissions) (updated for c472eec)

  • Gather context (PR diff, current file state)
  • Verify persistent 🔴 findings from prior reviews
  • Review what c472eec introduced
  • Check nit findings (colon-prefix push delete, git -C gap, etc.)
  • Post updated review

What c472eec fixed

  • git checkout -B / git switch -C (branch-reset): All expected forms denied in both shell flavors (lines 114, 117, 158, 161 for Bash; lines 193–194, 239–240 for PowerShell).
  • git commit -n (short no-verify): All positional forms (commit -n *, commit * -n *) denied in both shells (lines 129–131 Bash; 211–213 PowerShell).

🔴 Open — Bash(git branch -D *) still absent

This has been flagged in both prior reviews and remains unaddressed. The PowerShell block correctly has:

"PowerShell(git branch -D *)",   // line 185
"PowerShell(git branch -D)",     // line 186

The Bash block ends at line 112 ("Bash(git branch -fd *)") — all long-form permutations are present but -D (the most common real-session form) is missing. An agent running in Bash can execute git branch -D feat/some-branch today and the floor does not block it.

Insert after line 112 to maintain alphabetical order within the Bash git branch block:

"Bash(git branch -D *)",
"Bash(git branch -D)",

Fix this →


🔴 Open — Bash(dotnet user-secrets:*) is still a no-op

Line 70 is unchanged across all three commits:

"Bash(dotnet user-secrets:*)",

The colon is not valid dotnet CLI syntax. dotnet user-secrets set, list, remove, and clear are all invoked with a space separator. This pattern will never match any real session invocation. Replace with:

"Bash(dotnet user-secrets *)",

Fix this →


🟡 New — Bash(git push -n *) denies dry-run pushes, not hook bypass

Line 146 was added by c472eec:

"Bash(git push -n *)",

git push -n is --dry-run (simulates the push without sending data), not --no-verify. The hook-bypass form is already fully covered by "Bash(git push*--no-verify*)" (line 147). Denying dry-run push blocks a legitimate non-destructive pre-flight check with no security benefit. If intentional, the rationale should be documented; otherwise this should be removed.


🟡 New — PowerShell git switch --force/-f/--discard-changes gap

c472eec added three Bash deny entries for force-switching branches:

"Bash(git switch --discard-changes *)",   // line 159
"Bash(git switch --force *)",             // line 160
"Bash(git switch -f *)",                  // line 162

The PowerShell block has only git switch -C and git switch * -C (lines 239–240). The --force/-f/--discard-changes equivalents for PowerShell agents are absent, leaving Windows sessions able to force-switch without a deny. The git * push --force and git * reset --hard PowerShell wildcard-prefix rules (lines 172–174) don't cover switch. If the Bash coverage is intentional, PowerShell parity is incomplete.


🟡 Carried nits (from prior reviews, still open)

  • git push origin :branch (colon-prefix remote delete) — the older ref-deletion syntax (git push <remote> :<ref>) is uncovered in both shells. Adding "Bash(git push * :*)" and "PowerShell(git push * :*)" would close it.
  • git -C /path prefix for Bash destructive ops — PowerShell deny covers git * clean --force, git * push --force, etc. (lines 164–176). Bash has no equivalent; git -C /repo clean --force bypasses the Bash deny floor.
  • Read(**/.claude.json) (line 245): .claude.json is not a current Claude Code filename. If this is defensive against a historical or third-party pattern, the README's deny description should document the intent.
  • PowerShell write-op posture: The README mentions "PowerShell read-parity" but Windows agents will prompt for every git commit, git push, git add, git checkout, and git switch. Worth stating the platform asymmetry explicitly in the README.

Summary: c472eec correctly closes the branch-reset and short no-verify gaps. The two 🔴 findings — Bash(git branch -D *) absence and Bash(dotnet user-secrets:*) no-op — have now been flagged across three consecutive reviews and remain the blockers for this to function as a reliable security floor. The git push -n addition is a new minor concern worth confirming. The nit set is unchanged from prior reviews.

… --dry-run, not a hook bypass

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@claude

claude Bot commented Jul 20, 2026

Copy link
Copy Markdown

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


Code Review — feat(claude-permissions) (updated for 7e82d3f)

  • Gather context (PR diff, changed files)
  • Check what changed in 7e82d3f
  • Verify prior 🔴 findings
  • Assess correctness of git push -n removal
  • Synthesize and post updated review

What 7e82d3f changed

7e82d3f removes "Bash(git push -n *)" from the deny list. This is correct. For git push, -n is --dry-run (a trial run that sends nothing), not --no-verify. The no-verify bypass for push is already covered by "Bash(git push*--no-verify*)". Denying dry-run was overly restrictive and semantically wrong; removing it is the right call.


🔴 Still Open — Bash(git branch -D *) absent

Flagged in both prior reviews, still unaddressed. The PowerShell block correctly has:

"PowerShell(git branch -D *)",
"PowerShell(git branch -D)",

The Bash block covers all long-form permutations (--delete --force, --delete -f, -d --force, -df, -fd, etc.) but is missing the shorthand. git branch -D feat/some-branch in a Bash session is not blocked by this floor — the most common real-session form of force branch deletion goes through unimpeded.

Insert after "Bash(git branch -fd *)" (line 112):

"Bash(git branch -D *)",
"Bash(git branch -D)",

Fix this →


🔴 Still Open — Bash(dotnet user-secrets:*) is a no-op

Line 70 is unchanged across all four commits:

"Bash(dotnet user-secrets:*)",

The dotnet CLI uses space-separated subcommands: dotnet user-secrets set MyKey MyValue, dotnet user-secrets remove, dotnet user-secrets list, dotnet user-secrets clear. The colon form does not exist in the dotnet CLI — it resembles MSBuild target syntax but user-secrets is a dotnet tool and is never invoked with a colon. This pattern will never match any real session invocation, leaving all user-secrets manipulation to prompt (or pass unblocked if the allow list covers it). Replace with:

"Bash(dotnet user-secrets *)",

Fix this →


🟡 Nits (carried, still open)

  • git push origin :branch colon-prefix delete: git push --delete and git push -d are now fully covered, but git push origin :branch-name (the older refspec deletion syntax) is absent from both shell deny lists. Adding "Bash(git push * :*)" and the PowerShell equivalent closes the last remote-branch-deletion bypass.
  • git -C /path prefix for Bash destructive operations: PowerShell deny includes wildcard prefix forms (git * clean --force, git * push --force, etc.). Bash has none. An agent using git -C /repo clean --force bypasses the Bash floor.
  • Read(**/.claude.json) (line 232): .claude.json is not a current Claude Code configuration filename. If this is defensive against a historical or third-party pattern, a README note would explain the intent.
  • PowerShell write-op posture: Windows agents will prompt for every git commit, git push, git add, git checkout, and git switch — the README says "PowerShell read-parity" without surfacing this implication.

Summary: 7e82d3f correctly retracts the push dry-run deny (the -n reasoning is sound). The two 🔴 findings — Bash(git branch -D *) absence and the dotnet user-secrets:* no-op — have survived all four commits and remain the blockers for treating this as a reliable security floor.

@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: 7e82d3f881

ℹ️ 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
Comment thread components/claude-permissions/claude-permissions.json
Comment thread components/claude-permissions/claude-permissions.json
Comment thread components/claude-permissions/claude-permissions.json
…eat model

Deny additions (both shell forms): git diff --no-index secret reads,
forced fetch (--force/-f/+refspec force syntax), terminal-position
checkout -f/--force, bare and terminal commit -n. README gains an
explicit threat-model boundary: the glob grammar pins common destructive
spellings and is one defense-in-depth layer — not a sandbox; flag
permutation is structurally unclosable by rules, and the classifier-side
command decomposition gap is the recorded structural fix. Observed new
spellings land on sight; adversarial enumeration beyond common forms is
out of scope.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@claude

claude Bot commented Jul 20, 2026

Copy link
Copy Markdown

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


Code Review — feat(claude-permissions) (updated for af65224)

  • Read current file state (af65224)
  • Verify persistent 🔴 findings from prior reviews
  • Review new additions in af65224
  • Synthesize and post updated review

What af65224 fixed

af65224 correctly addresses all four round-3 Codex P1 findings in both shell flavors:

  • git diff --no-index secret bypass: "Bash(git diff --no-index*)" and "Bash(git diff * --no-index*)" (PowerShell: lines 228–229) — closes the path where git diff --no-index .env /dev/null would have printed a secret file through the broad git diff * allow.
  • Forced fetch variants: All five forms (+refspec, --force*, -f in leading and trailing positions) denied in both shells (lines 138–142, 230–234) — closes local-ref force-overwrite via git fetch --force origin +refs/heads/main:refs/heads/target.
  • Terminal-position checkout force flags: "Bash(git checkout * --force)" and "Bash(git checkout * -f)" (lines 114, 116; PowerShell lines 203, 205) — previously only prefix-position coverage existed; git checkout main -f now denied.
  • Bare and terminal commit -n: All four forms (-n *, -n, * -n *, * -n) in both shells (lines 131–134, 223–226) — the Codex finding that bare git commit -n and terminal git commit -m msg -n were uncovered is closed.
  • README threat model section: The new prose in README.md accurately scopes the floor as one defense-in-depth layer, names the structural classifier gap, and sets correct expectations for permutation coverage. This is the right framing.

🔴 Still open — Bash(git branch -D *) absent (fifth review)

This has been flagged in every review since the original submission. The PowerShell block at lines 195–196 has:

"PowerShell(git branch -D *)",
"PowerShell(git branch -D)",

The Bash block at lines 103–112 covers every long-form permutation but not -D. git branch -D feat/some-branch in a Bash session is not blocked — this is the most common real-session form of force branch deletion and the floor's most visible gap.

Add after line 112 to maintain order within the Bash git branch block:

"Bash(git branch -D *)",
"Bash(git branch -D)",

Fix this →


🔴 Still open — Bash(dotnet user-secrets:*) is a no-op (fifth review)

Line 70 is unchanged across all five commits:

"Bash(dotnet user-secrets:*)",

The dotnet CLI uses space-separated subcommands — dotnet user-secrets set, list, remove, clear. The colon form is not valid dotnet CLI syntax; it resembles MSBuild target notation but user-secrets is a dotnet global tool and is never invoked with a colon. This pattern has matched zero real invocations since the component was created and never will. Replace with:

"Bash(dotnet user-secrets *)",

Fix this →


🟡 Nits (carried, still open)

  • git push origin :branch colon-prefix remote deletegit push --delete and git push -d are fully covered; the older git push <remote> :<ref> refspec syntax that deletes a remote ref is absent from both shell deny lists. "Bash(git push * :*)" and the PowerShell equivalent close it.
  • PowerShell(git switch --force/-f/--discard-changes) absentc472eec added Bash deny coverage for git switch --force, --discard-changes, and -f; PowerShell has only -C forms (lines 260–261). Windows agents can force-switch without a deny.
  • git -C /path prefix for Bash destructive ops — PowerShell deny covers git * clean --force, git * push --force, git * reset --hard, etc. (lines 174–186). Bash has no equivalent wildcard-prefix forms; git -C /repo clean --force bypasses the Bash floor.
  • Read(**/.claude.json) (line 266) — .claude.json is not a current Claude Code configuration filename. If this is defensive against a historical or third-party pattern, a README note on the deny description would document the intent.
  • PowerShell write-op posture — README calls out "PowerShell read-parity" without surfacing that Windows agents will prompt for every git commit, git push, git add, git checkout, and git switch. The threat-model section would be a natural place to state the platform asymmetry explicitly.

Summary: af65224 correctly closes all four round-3 gaps and the README threat-model section is well-judged — it accurately scopes what the floor can and cannot provide. The two 🔴 findings, both present since the original submission, remain the only blockers: Bash(git branch -D *) is the highest-frequency real-session bypass, and Bash(dotnet user-secrets:*) is a dead rule. Both are one-line fixes.

@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: af6522491c

ℹ️ 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
Comment thread components/claude-permissions/claude-permissions.json
Comment thread components/claude-permissions/claude-permissions.json
…etches

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@claude

claude Bot commented Jul 20, 2026

Copy link
Copy Markdown

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


Code Review — feat(claude-permissions) (updated for 911decf)

  • Gather PR context and commit history
  • Read current file state at 911decf
  • Verify persistent 🔴 findings from prior reviews
  • Review what 911decf introduced (mirror pushes, mid-position forced fetches)
  • Check remaining nit findings
  • Synthesize and post updated review

What 911decf fixed

911decf correctly addresses both Codex P1 findings raised after af65224:

  • Mirror push: "Bash(git push * --mirror*)", "Bash(git push --mirror*)", "PowerShell(git push * --mirror*)", "PowerShell(git push --mirror*)" (lines 148, 156, 243, 251) — closes the gap where git push --mirror origin would have matched the broad Bash(git push *) allow and deleted remote refs absent locally.
  • Mid-position forced fetch: "Bash(git fetch * -f *)" (line 140) and "PowerShell(git fetch * -f *)" (line 235) — closes the gap where git fetch origin -f refs/heads/main:refs/heads/victim did not match any prior fetch deny rule because -f appeared after the remote argument.

Both fixes are correct and cover the bare and mid-command forms in both shells.


🔴 Still open — Bash(git branch -D *) absent (sixth review)

This has been flagged in every review since the original submission and across all six commits. The PowerShell block at lines 198–199 correctly has:

"PowerShell(git branch -D *)",
"PowerShell(git branch -D)",

The Bash block at lines 103–112 covers all long-form permutations (--delete --force, --delete -f, -d --force, -df, -fd, etc.) but -D is still absent. git branch -D feat/some-branch in a Bash session is not blocked. This is the most common real-session form of force branch deletion.

Add after line 112 to maintain order within the Bash git branch block:

"Bash(git branch -D *)",
"Bash(git branch -D)",

Fix this →


🔴 Still open — Bash(dotnet user-secrets:*) is a no-op (sixth review)

Line 70 is unchanged across all six commits:

"Bash(dotnet user-secrets:*)",

The dotnet CLI uses space-separated subcommands — dotnet user-secrets set, list, remove, clear. The colon form does not exist in the dotnet CLI (it resembles MSBuild target syntax, but user-secrets is a dotnet tool and is never invoked with a colon). This pattern has matched zero real invocations since the component was created. Replace with:

"Bash(dotnet user-secrets *)",

Fix this →


🟡 Nits (carried, still open)

  • git push origin :branch colon-prefix remote deletegit push --delete and git push -d in all positional forms are fully covered. The older refspec syntax git push <remote> :<ref> that deletes a remote branch is still absent from both shell deny lists. "Bash(git push * :*)" and the PowerShell equivalent would close the last remote-branch-deletion bypass.
  • PowerShell(git switch --force/-f/--discard-changes) absentlines 266–267 cover only -C for PowerShell switch. The Bash deny covers --force, --discard-changes, and -f (lines 172–175); Windows agents have no equivalent coverage and can force-switch without a prompt.
  • git -C /path prefix for Bash destructive ops — The PowerShell deny covers wildcard-prefix forms (git * clean --force, git * push --force, git * reset --hard, git * restore ., git * restore :/) at lines 177–189. Bash has no equivalent; git -C /repo clean --force bypasses the Bash deny floor today.
  • Read(**/.claude.json) (line 272) — .claude.json is not a current Claude Code configuration filename (.claude/settings.json and .claude/settings.local.json are covered separately). If this is defensive against a third-party tool or historical pattern, a README note would document the intent.
  • PowerShell write-op posture — The README's "allow" description mentions both shells but does not explicitly state that Windows agents running in PowerShell will prompt for every git commit, git push, git add, git checkout, and git switch. The threat-model section would be a natural place to state this platform asymmetry.

Summary: 911decf correctly closes the mirror push and mid-position forced fetch gaps. The two 🔴 findings — Bash(git branch -D *) absence and the Bash(dotnet user-secrets:*) no-op — have now survived all six commits and remain the only blockers for this to function as the reliable security floor the PR describes. Both are one-line fixes.
| Branch

@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: 911decf168

ℹ️ 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
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@claude

claude Bot commented Jul 20, 2026

Copy link
Copy Markdown

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


Code Review — feat(claude-permissions) (updated for e23b5a5)

  • Read current file state at e23b5a5
  • Verify persistent 🔴 findings from prior reviews
  • Review what e23b5a5 introduced
  • Post updated review

What e23b5a5 fixed

e23b5a5 correctly addresses the Codex P1 finding raised after 911decf:


🔴 Still Open — Bash(git branch -D *) absent (seventh review)

This has been flagged in every prior review and has survived seven commits unchanged. The PowerShell block correctly has at lines 200–201:

"PowerShell(git branch -D *)",
"PowerShell(git branch -D)",

The Bash block at lines 103–112 covers all long-form permutations (--delete --force, --delete -f, -d --force, -df, -fd, etc.) but -D is still absent. git branch -D feat/some-branch in a Bash session is not blocked — this is the most common real-session form of force branch deletion.

Add after line 112 to maintain order within the Bash git branch block:

"Bash(git branch -D *)",
"Bash(git branch -D)",

Fix this →


🔴 Still Open — Bash(dotnet user-secrets:*) is a no-op (seventh review)

Line 70 is unchanged across all seven commits:

"Bash(dotnet user-secrets:*)",

The dotnet CLI uses space-separated subcommands — dotnet user-secrets set, list, remove, clear. The colon form does not exist in the dotnet CLI. This pattern has matched zero real invocations since the component was created and never will. Replace with:

"Bash(dotnet user-secrets *)",

Fix this →


🟡 Nits (carried, still open)

  • git push origin :branch colon-prefix remote deletegit push --delete and git push -d in all positional forms are fully covered. The older refspec syntax git push <remote> :<ref> that deletes a remote branch is still absent from both shell deny lists. "Bash(git push * :*)" and the PowerShell equivalent close the last remote-branch-deletion bypass.
  • PowerShell(git switch --force/-f/--discard-changes) absentlines 268–269 cover only -C for PowerShell switch. The Bash deny covers --force, --discard-changes, and -f (lines 172–175); Windows agents have no equivalent coverage and can force-switch without a prompt.
  • git -C /path prefix for Bash destructive ops — The PowerShell deny covers wildcard-prefix forms (git * clean --force, git * push --force, git * reset --hard, git * restore ., git * restore :/) at lines 179–191. Bash has no equivalent; git -C /repo clean --force bypasses the Bash deny floor today.
  • Read(**/.claude.json) (line 276) — .claude.json is not a current Claude Code configuration filename (.claude/settings.json and .claude/settings.local.json are covered separately at lines 277, 296). If this is defensive against a third-party tool or historical pattern, a README note would document the intent.
  • PowerShell write-op posture — The README does not explicitly state that Windows agents running in PowerShell will prompt for every git commit, git push, git add, git checkout, and git switch. The threat-model section would be a natural place to state this platform asymmetry.

Summary: e23b5a5 correctly closes the git worktree add -B branch-reset gap in both shells and both positional forms. The two 🔴 findings — Bash(git branch -D *) absence and the Bash(dotnet user-secrets:*) no-op — have now survived all seven commits and remain the only blockers for this to function as the reliable security floor the PR describes. Both are one-line fixes.
| Branch

@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: e23b5a5ee8

ℹ️ 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
Comment thread components/claude-permissions/claude-permissions.json
…discard flags

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@claude

claude Bot commented Jul 20, 2026

Copy link
Copy Markdown

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


Code Review — feat(claude-permissions) (updated for a87d482)

  • Gather context (current file state at a87d482)
  • Verify persistent 🔴 findings from prior reviews
  • Review what a87d482 introduced
  • Synthesize and post updated review

What a87d482 fixed

a87d482 correctly closes the two Codex P1 findings raised after e23b5a5:

  • Terminal-position git add -f/--force: "Bash(git add * --force)", "Bash(git add * -f)" added (Bash lines 100, 102); "PowerShell(git add * --force)", "PowerShell(git add * -f)" added (PowerShell lines 198, 200) — closes the gap where git add . -f or git add .env --force (flag trailing the path) matched the broad allow without hitting a deny.

  • Terminal-position git switch --force/-f/--discard-changes: "Bash(git switch * --discard-changes)", "Bash(git switch * --force)", "Bash(git switch * -f)" added (Bash lines 173–176); same three entries added for PowerShell (lines 275–278) — closes the gap where git switch other -f or git switch other --discard-changes (flag trailing the branch) matched the broad allow.

Both fixes are correct, cover the correct positional forms, and apply symmetrically to both shell flavors.


🔴 Still Open — Bash(git branch -D *) absent (eighth review)

This has been flagged in every review across all eight commits. The PowerShell block at lines 207–208 correctly has:

"PowerShell(git branch -D *)",
"PowerShell(git branch -D)",

The Bash block at lines 105–114 covers all long-form permutations (--delete --force, --delete -f, -d --force, -df, -fd, etc.) but -D is still absent. git branch -D feat/some-branch in a Bash session is not blocked — this is the most common real-session form of force branch deletion, and the floor's most visible gap.

Add after line 114 to maintain order within the Bash git branch block:

"Bash(git branch -D *)",
"Bash(git branch -D)",

Fix this →


🔴 Still Open — Bash(dotnet user-secrets:*) is a no-op (eighth review)

Line 70 is unchanged across all eight commits:

"Bash(dotnet user-secrets:*)",

The dotnet CLI uses space-separated subcommands: dotnet user-secrets set, list, remove, clear. The colon form does not exist in the dotnet CLI (it resembles MSBuild target syntax, but user-secrets is a dotnet tool invoked without a colon). This pattern has matched zero real invocations since the component was created and never will. Replace with:

"Bash(dotnet user-secrets *)",

Fix this →


🟡 Nit — PowerShell git switch still missing leading-position force forms

a87d482 added the three terminal-position PowerShell switch deny entries (lines 275–278). The Bash deny list is now symmetric: it covers both leading-position ("Bash(git switch --force *)", "Bash(git switch -f *)", "Bash(git switch --discard-changes *)" at lines 177–180) and terminal-position forms. The PowerShell deny list has the terminal forms but is still missing the three leading-position equivalents:

"PowerShell(git switch --discard-changes *)",
"PowerShell(git switch --force *)",
"PowerShell(git switch -f *)",

A PowerShell agent running git switch --force feat/some-branch (flag before the branch name) is not covered.


🟡 Nits (carried from prior reviews, still open)

  • git push origin :branch colon-prefix remote deletegit push --delete and git push -d in all positional forms are fully covered. The older refspec syntax git push <remote> :<ref> that deletes a remote branch is absent from both shell deny lists. "Bash(git push * :*)" and the PowerShell equivalent would close the last remote-branch-deletion bypass.
  • git -C /path prefix for Bash destructive ops — The PowerShell deny covers wildcard-prefix forms (git * clean --force, git * push --force, git * reset --hard, git * restore ., git * restore :/) at lines 184–196. Bash has no equivalent; git -C /repo clean --force bypasses the Bash deny floor.
  • Read(**/.claude.json) (line 286) — .claude.json is not a current Claude Code configuration filename (.claude/settings.json and .claude/settings.local.json are covered separately). If this is defensive against a third-party tool or historical pattern, a README note would document the intent.
  • PowerShell write-op posture — The README does not explicitly state that Windows agents running in PowerShell will prompt for every git commit, git push, git add, git checkout, and git switch. The threat-model section would be a natural place to state this platform asymmetry.

Summary: a87d482 correctly closes the terminal-position forced add and switch force/discard gaps in both shell flavors. The two 🔴 findings — Bash(git branch -D *) absence and the Bash(dotnet user-secrets:*) no-op — have now survived all eight commits and remain the only blockers for this to function as the reliable security floor the PR describes. Both are one-line fixes.
| Branch

@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: a87d4822c0

ℹ️ 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/README.md
Comment thread components/claude-permissions/claude-permissions.json
@kyle-sexton
kyle-sexton merged commit 0916e02 into main Jul 20, 2026
42 checks passed
@kyle-sexton
kyle-sexton deleted the feat/claude-permission-floor branch July 20, 2026 18:00
kyle-sexton added a commit to melodic-software/claude-code-plugins that referenced this pull request Jul 20, 2026
… once, never self-apply (#730)

## Summary

Ships #495's three fix directions:

1. **Reviewed permissions recipe by pointer**:
`reference/permission-preflight.md` cites the standards
`claude-permissions` component (the fleet's canonical allow/deny floor,
merged today: 60 allow incl. PowerShell read-parity, 244 deny) composed
operator-side via the dotfiles chezmoi seam — no restated list to drift.
2. **Trusted worktree-root guidance**: the sibling out-of-tree worktree
root plus the matching `permissions.additionalDirectories` entry,
root-agnostic.
3. **Loop-start preflight check**: `skills/work/scripts/preflight.sh`
reports ONCE, up front — cwd-not-a-repo (note), probed core verbs (`git
commit`, `git push`, `gh pr create`, `gh issue comment`) uncovered by
any allow rule, worktree root not in `additionalDirectories`. Always
exits 0 (report-only), `--count` for scripted gating, no live permission
probe. **Never self-applies**: the auto-mode classifier blocks an agent
editing its own `permissions.allow` (empirically hit during #495's own
remediation attempt), so the check detects and points at operator-side
remediation. Wired as the work skill's first loop-start action;
babysit-prs applicability noted by pointer (no cross-plugin edit).

28/28 hermetic test cases;
shellcheck/shfmt/markdownlint/typos/validate-plugin-contracts/changelog-parity
all green. Live smoke on this machine correctly flagged two real gaps.
Version 0.17.0 (composed above the concurrently-merged 0.16.0
container-verbs entry).

## Related

- melodic-software/standards#210 (the claude-permissions floor this
consumes)
- melodic-software/dotfiles#242 (the composition seam the remediation
points at)
- #697 (the mining evidence for the report-only constraint)

Closes #495

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
kyle-sexton added a commit that referenced this pull request Jul 21, 2026
## 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.com/claude-code)

https://claude.ai/code/session_01U9zbRxtRuTx1WsQavFc2Gi

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
kyle-sexton added a commit that referenced this pull request Jul 22, 2026
…tomerge-alert (#251)

No related issue: caller rollout for merged
melodic-software/ci-workflows#213 (issue #210 closed)

## Related
ci-workflows#210, ci-workflows#213, ci-workflows#223

## Summary
- standards owns the sync trigger point and already holds the App
secrets `sync.yml` uses, so it hosts the scheduled caller for the
stuck-automerge watchdog reusable added in
melodic-software/ci-workflows#213, rather than medley.
- **This PR is a draft, blocked on melodic-software/ci-workflows#223.**
The pinned SHA (`dd45dacd7b74dd05f3334b78769e57225f7356d8`) is the
reusable as merged in #213, which authors its tracking issue with the
caller's ambient `GITHUB_TOKEN` and therefore needs a caller-granted
`issues: write`. That combination cannot satisfy `runner-policy`'s
write-caller-permissions invariant (identity-passthrough-only secret
mapping, no hyphens) against this reusable's kebab-case
`app-client-id`/`app-private-key` secret inputs — see #223 for the full
diagnosis and the fix (the reusable mints its own App token for issue
writes instead).
- The workflow file and `policy.json` entry here are already written for
the **post-#223 shape**: no caller `issues: write`, no
`allowedCallerPermissions` waiver, secrets-only contract. Only the
pinned SHA (in both the workflow file and the policy.json key) needs to
flip to #223's merge SHA once it lands — no other change.

## Test plan
- [x] `npm run test:runner-policy` — 228/228 pass
- [x] `npm run lint:runner-policy` — Runner policy passed
- [x] `actionlint
.github/workflows/standards-sync-stuck-automerge-alert.yml` — clean
- [x] `zizmor
.github/workflows/standards-sync-stuck-automerge-alert.yml` — no
findings
- [x] `npx biome check components/runner-policy/policy.json` — no fixes
needed
- [ ] Flip pinned SHA to ci-workflows#223's merge SHA once it merges,
mark ready for review

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

https://claude.ai/code/session_01KYvF6bWGqemS9aYFfWJRiW

---------

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