chore(perms): allow git push, keep blocking force pushes#202
Conversation
Replace the blanket git push deny with force-push-only denies so feature branches can be pushed while history rewrites stay blocked.
There was a problem hiding this comment.
Code Review
This pull request modifies the deny list in .claude/settings.json to replace the broad git push block with a specific block for git push -f. However, the review feedback highlights a critical security bypass: because of how Claude Code parses commands, the proposed patterns will fail to match and block force pushes. Additionally, naive wildcard matching for -f could accidentally block common branch names containing -f. A more robust set of patterns is suggested to safely and effectively block force pushes.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| "Bash(git push --force:*)", | ||
| "Bash(git push -f:*)", |
There was a problem hiding this comment.
⚠️ Critical Security Bypass & Usability Issue
The current patterns will fail to block force pushes and could also block normal pushes if implemented incorrectly.
1. Why the current patterns fail (Security Bypass)
Claude Code's permission system parses commands into a command/subcommand part (before the colon :) and an arguments part (after the colon :).
- For any
git pushcommand (e.g.,git push --forceorgit push origin main -f), the command part is parsed asgit push. - Therefore, Claude Code will look for permission rules matching
Bash(git push:<arguments>). - Because your patterns expect the command part itself to be
git push --forceorgit push -f(before the colon), they will never match. This means all force pushes will be silently allowed without any block or prompt!
2. The danger of naive wildcard matching (Usability Issue)
If we try to fix this by simply using Bash(git push:*-f*), it will match any argument containing -f. This includes extremely common branch names like:
feature-flagsrefactor-formsadd-fieldsuser-feedback
This would completely break the ability to push these branches, causing major frustration.
3. The Solution
To safely and comprehensively block force pushes without false positives, we must target the arguments of git push specifically:
- Block
--forceand--force-with-leaseanywhere in the arguments. - Block
-fonly when it is a standalone flag (at the start, middle, or end of the arguments, or as the sole argument).
"Bash(git push:*--force*)",
"Bash(git push:*--force-with-lease*)",
"Bash(git push:-f)",
"Bash(git push:-f *)",
"Bash(git push:* -f)",
"Bash(git push:* -f *)"There was a problem hiding this comment.
Pull request overview
Narrows the Claude Code permission denies in .claude/settings.json to allow non-force git push operations (e.g., pushing feature branches) while intending to keep history-rewriting pushes blocked.
Changes:
- Removed the blanket deny rule for all
git pushinvocations. - Kept/added deny rules targeting force pushes via
--forceand-f.
| "Bash(rm -rf:*)", | ||
| "Bash(git push:*)", | ||
| "Bash(git push --force:*)", | ||
| "Bash(git push -f:*)", | ||
| "Bash(npm publish:*)", | ||
| "Bash(pnpm publish:*)" |
The flag-adjacent deny patterns only matched when --force/-f immediately followed 'push', so 'git push origin main --force' and --force-with-lease slipped through. Use space-form wildcard patterns (colons are literal mid-pattern per Claude Code permission matching) to catch the flags anywhere without matching branch names like feature-flags.
Summary
Narrows the git-push permission rules in
.claude/settings.jsonso Claude Code can push feature branches, while history-rewriting pushes stay hard-denied.Before:
After:
Why
The blanket
Bash(git push:*)deny is a hard block (not overridable via the permission prompt), so the agent couldn't push any branch — every push had to be run manually. Force pushes remain blocked to protect branch history, and server-side branch protection onmainis unchanged as the backstop.npm publish/pnpm publish/rm -rfdenies are untouched.