Block git push --force in Claude Code with a deny rule
The deny rules that stop Claude Code force-pushing, how to cover -f and --force-with-lease and reordered options, and why a deny rule is a safety net rather than a wall.
Let Claude Code drive Git and sooner or later a conflict resolution ends in git push --force, taking someone else's commits with it. The way to stop that is a permissions.deny entry in settings.json.
Four lines in your project's .claude/settings.json cover the common spellings.
KEY POINT
What you will learn
- The deny rules that block a force push
- How to cover
-f,--force-with-leaseand reordered options - What a deny rule does not protect against
The rules
{
"permissions": {
"deny": [
"Bash(git push --force:*)",
"Bash(git push -f:*)",
"Bash(git push --force-with-lease:*)",
"Bash(git push * --force:*)"
]
}
}
Bash rules match the command text, with * standing in for any text. Bash(git push --force:*) therefore matches git push --force origin main and anything else beginning that way.
用語解説
Deny precedence: rules are evaluated deny, then ask, then allow, and the first match decides. Rule specificity does not change that order, so an allow rule can never carve an exception out of a deny rule. Deny rules also apply in every permission mode, including bypassPermissions, where allow rules have no effect at all.
Covering the spellings
Because the match is against the command text, a different option order does not hit the same rule. These are the forms worth listing:
| Command | Pattern that matches it |
|---|---|
git push --force origin main | Bash(git push --force:*) |
git push -f origin main | Bash(git push -f:*) |
git push origin main --force | Bash(git push * --force:*) |
git push --force-with-lease | Bash(git push --force-with-lease:*) |
--force-with-lease is safer than a bare --force, but it still overwrites the remote. If your team allows it, move that one line into ask rather than deny.
A deny rule is a safety net, not a wall
cd repo && git push --force, or a push buried in a shell script, does not start with the text your rule matches. For a branch that genuinely must not be rewritten, set branch protection on the remote — GitHub's "do not allow force pushes" — and treat the deny rule as what it is: a way to reduce local accidents.
Confirming it works
- Save the settings and start (or restart) Claude Code.
- Run
/permissionsand check that the four lines appear under deny, along with the settings file each came from. - Ask Claude to run
git push --force origin test-branch. - The call should be refused, with the reason shown.
Related rules
The same approach covers other commands that destroy work:
{
"permissions": {
"deny": [
"Bash(git reset --hard:*)",
"Bash(git checkout -- .:*)",
"Bash(git clean -f:*)"
]
}
}
For the overall rule design — allow, ask and deny, and how the scopes interact — see Claude Code permissions in settings.json. For a file-oriented example of the same mechanism, see Stop Claude Code reading your .env.
Summary
- Put
Bash(git push --force:*)and its siblings inpermissions.denyin.claude/settings.json - The match is on command text, so
-f,--force-with-leaseand reordered options each need a line - Deny always beats allow, in every mode, and personal settings cannot undo a project deny
- Pair it with branch protection on the remote for anything you truly cannot lose
FAQ
- Can Claude get around a deny rule by writing the command differently?
- Yes. Bash rules match the command text, so -f and --force-with-lease need their own entries. For a branch you truly must protect, add branch protection on the remote as well.
- Can someone override the deny in their personal settings?
- No. Deny beats allow in every mode, including bypassPermissions, and a deny in the project's settings.json cannot be undone by an allow in settings.local.json.
Primary sources
This article was drafted by AI from official documentation and reviewed by the site operator before publishing. Found a mistake? Let us know via the contact page.