Block git push --force in Claude Code with a deny rule

Claude Code Published: Updated:

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.

Verified on Sep 25, 2026 These tools change quickly. Please also check the latest official documentation.
Contents
  1. The rules
  2. Covering the spellings
  3. Confirming it works
  4. Related rules
  5. Summary

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-lease and 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:

CommandPattern that matches it
git push --force origin mainBash(git push --force:*)
git push -f origin mainBash(git push -f:*)
git push origin main --forceBash(git push * --force:*)
git push --force-with-leaseBash(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

  1. Save the settings and start (or restart) Claude Code.
  2. Run /permissions and check that the four lines appear under deny, along with the settings file each came from.
  3. Ask Claude to run git push --force origin test-branch.
  4. The call should be refused, with the reason shown.

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 in permissions.deny in .claude/settings.json
  • The match is on command text, so -f, --force-with-lease and 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.