Claude Code permissions by example, auto-allow npm scripts, confirm git push, block force push

Claude Code Published:

One settings.json that lets npm run scripts go without prompts while npm install still asks, stops right before every git push, and denies git push --force in all its spellings.

Verified on Oct 10, 2026 These tools change quickly. Please also check the latest official documentation. How articles are researched and verified
Contents
  1. The finished configuration
  2. 1. Auto-allow npm scripts (allow)
    1. Patterns per package manager
    2. For a Python project
  3. 2. Confirm every git push (ask)
    1. Auto-allowing one branch only
  4. 3. Block force push (deny)
  5. Verify it
  6. Related settings
  7. Summary

When you let Claude Code drive, you usually want three lines drawn. Scripts you wrote yourself, such as npm run lint and npm run test, should run without a prompt. git push reaches the remote, so you want one last look right before it. git push --force should never run at all.

All three fit in a single permissions block in settings.json, using the three layers allow, ask and deny. This article shows the finished configuration first, then explains each layer and the places people trip.

KEY POINT

What you will learn

  • A complete settings.json that combines allow, ask and deny
  • What the :* prefix match means, and how to keep install commands out of an allow rule
  • Why ask outranks allow, and why "don't ask again" still prompts
  • How to cover every spelling of a force push (-f, --force-with-lease, reordered options) with deny

The finished configuration

Put this in the project's .claude/settings.json. It is written to be shared with a team.

{
  "permissions": {
    "allow": [
      "Bash(npm run:*)",
      "Bash(npm test)",
      "Bash(npx tsc:*)",
      "Bash(npx vitest:*)",
      "Bash(npx eslint:*)",
      "Bash(npx prettier:*)",
      "Bash(git status)",
      "Bash(git diff:*)",
      "Bash(git log:*)",
      "Bash(git add:*)",
      "Bash(git commit:*)"
    ],
    "ask": [
      "Bash(git push:*)",
      "Bash(npm install:*)",
      "Bash(npm i:*)",
      "Bash(npm uninstall:*)"
    ],
    "deny": [
      "Bash(git push --force:*)",
      "Bash(git push -f:*)",
      "Bash(git push --force-with-lease:*)",
      "Bash(git push * --force:*)"
    ]
  }
}

With this in place, scripts and the read-only Git commands plus commits run silently, git push and npm install open a dialog, and a force push is refused.

用語解説

Rule precedence: rules are evaluated deny, then ask, then allow, and the first match in that order decides. Specificity does not change the order. An allow rule for Bash(git:*) does not stop an ask rule for Bash(git push:*) from prompting, and a deny match is refused in every permission mode.

1. Auto-allow npm scripts (allow)

In Bash(npm run:*), :* is a prefix match: "starts with npm run, anything may follow." It matches arguments too, such as npm run test -- --watch. npm install begins differently, so it is not swept in. Put the * after the subcommand; a rule with the * before it, like Bash(npm * test), triggers a startup warning.

npx can run any package, so the example allows only the ones you use rather than the whole program. Do not put Bash(npx:*) in ask: ask is evaluated before allow, so the npx tsc you just allowed would start prompting too. Leaving it out means "the ones I allowed run silently, any other npx follows the default behavior."

Patterns per package manager

ToolRunning scriptsInstall commands (ask)
npmBash(npm run:*)Bash(npm install:*), Bash(npm i:*)
pnpmBash(pnpm run:*), Bash(pnpm test), Bash(pnpm lint)Bash(pnpm add:*), Bash(pnpm install:*)
yarnBash(yarn run:*), Bash(yarn test)Bash(yarn add:*), Bash(yarn install)
bunBash(bun run:*)Bash(bun add:*), Bash(bun install:*)

pnpm and yarn let you drop run, as in pnpm lint, so either list the scripts you use or allow the whole program and stop the install commands with ask rules. Because ask is evaluated first, that combination is safe:

{
  "permissions": {
    "allow": ["Bash(pnpm:*)"],
    "ask": ["Bash(pnpm add:*)", "Bash(pnpm install:*)", "Bash(pnpm remove:*)"]
  }
}

For a Python project

{
  "permissions": {
    "allow": ["Bash(pytest:*)", "Bash(python -m pytest:*)", "Bash(ruff:*)", "Bash(mypy:*)", "Bash(uv run:*)"],
    "ask": ["Bash(pip install:*)", "Bash(uv add:*)"]
  }
}

2. Confirm every git push (ask)

An ask rule means "prompt me even if an allow rule matches." Automate the read-only Git commands and commits with allow, put Bash(git push:*) in ask, and only the push stops.

The dialog at push time offers these options:

OptionEffect
Allow onceRuns this one call
Don't ask againAppends an allow rule to settings.local.json, but you are still prompted next time while the ask rule remains
DenyDoes not run, and lets you tell Claude why

That "don't ask again" leaves the ask rule in place is intended. It keeps you from undoing "I always review pushes" with one keystroke. To stop the prompts, remove the ask line from the settings file.

Auto-allowing one branch only

If pushes to your own working branches should go through, scope both the allow and the ask rule by branch:

{
  "permissions": {
    "allow": ["Bash(git push origin feature/*:*)"],
    "ask": ["Bash(git push origin main:*)", "Bash(git push origin develop:*)"]
  }
}

This depends on Claude spelling the command as git push origin feature/xxx. A bare git push matches neither rule and falls through to your defaultMode. If you value reliability over convenience, skip the branch scoping and keep one "always confirm pushes" rule.

3. Block force push (deny)

Deny is evaluated before ask and allow, and it applies in every permission mode, including bypassPermissions, where allow rules have no effect. A deny in the project's settings.json cannot be undone by an allow in someone's settings.local.json, which is why team-wide prohibitions belong here.

Bash rules match the command text, so each spelling needs its own line:

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.

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 a way to reduce local accidents.

The same approach covers other commands that destroy work:

{
  "permissions": {
    "deny": ["Bash(git reset --hard:*)", "Bash(git checkout -- .:*)", "Bash(git clean -f:*)"]
  }
}

Verify it

  1. Save the settings and restart Claude Code.
  2. Run /permissions and check that each list shows the lines you wrote and the file each came from.
  3. Say "run the tests." If npm run test goes without a prompt, allow works.
  4. Say "add lodash." If npm install lodash prompts, ask works.
  5. Say "commit this and push it." The commit should go through and a dialog should appear right before the push.
  6. Ask Claude to run git push --force origin test-branch. It should be refused.

How settings.json and settings.local.json divide the work, how the scopes override each other and how to pick a defaultMode are covered in the parent article, Claude Code permissions in settings.json and settings.local.json. For the file-oriented version of the same mechanism, see Stop Claude Code reading your .env. Once scripts run unattended, a hook that lints after every edit keeps quality up: Run lint and format automatically with Claude Code hooks.

Summary

  • Rules are evaluated deny, then ask, then allow, and the first match decides
  • Bash(npm run:*) allows every npm script in one rule; npm install is a different prefix. When allowing a whole program such as pnpm or npx, stop the install commands with ask
  • Bash(git push:*) in ask forces a prompt right before every push, and "don't ask again" does not remove it
  • Deny --force, -f, --force-with-lease and the reordered form separately. Deny applies in every mode and cannot be undone by personal settings
  • Pair the deny rules with branch protection on the remote for anything you cannot lose

FAQ

Does Bash(npm run:*) also allow npm install?
No. The rule is a prefix match on npm run, and npm install starts differently. Only a rule for the whole program, such as Bash(npm:*), would cover install, in which case put the install commands in ask.
If allow has all of git and ask has git push, which wins?
Ask. Rules are evaluated deny, then ask, then allow, and the first match decides, so a broad allow rule does not stop the ask rule from prompting.
Can Claude get around a deny rule by spelling the command differently?
Yes. Bash rules match the command text, so -f, --force-with-lease and a different option order each need their own line. For a branch you truly must protect, add branch protection on the remote as well.
Can a personal settings.local.json undo a deny set by the project?
No. Deny rules are evaluated before ask and allow wherever they are written, and they apply in every permission mode, including bypassPermissions.

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.