Stop Claude Code, Cursor and Codex asking permission for every command
In short: the prompts go away when you tell each tool which actions are routine, not when you turn approvals off. Pre-approve the exact commands you run all day (tests, lint, build, git add/commit), let a sandbox or an auto-review mode handle the rest, and keep pushes, publishes and deletes behind a prompt. Below is the setup for Claude Code, Cursor and Codex, copy-paste configs for each, why an allowlisted command still asks, and a small script that flags allow rules that are broader than they look.
Why the prompts keep coming
All three tools ask by default before the agent runs a shell command it hasn't been cleared for. Each one gives you three ways to cut that down, and they're easy to mix up:
- An allowlist of exact commands or prefixes that run without asking. Deterministic, but only as good as the patterns you write.
- A reviewer mode where a second model judges each risky action instead of you: auto mode in Claude Code, Auto-review in Cursor and Codex.
- A sandbox that limits what a command can touch, so commands that stay inside it can run without asking.
Then there's the "no prompts at all" switch each tool has. That one removes the safety net instead of tuning it, and the docs for all three say to use it only in an isolated environment.
The quick answer for each tool
| Claude Code | Cursor | Codex | |
|---|---|---|---|
| Allowlist file | permissions.allow in .claude/settings.json or ~/.claude/settings.json | terminalAllowlist in .cursor/permissions.json or ~/.cursor/permissions.json | prefix_rule() in .codex/rules/*.rules or ~/.codex/rules/ |
| Rule format | Bash(npm run test *) | npm run test (prefix) | pattern = ["npm", "install"] |
| Reviewer mode | Auto mode (Shift+Tab) | Auto-review (the default since Cursor 3.6) | approvals_reviewer = "auto_review", or Approve for me in the app |
| Sandbox that skips prompts | /sandbox → auto-allow | Allowlist mode with sandboxing on | Built in: the default workspace-write sandbox |
| Still asks anyway | ask rules, in every mode | File-Deletion and External-File Protection | decision = "prompt" rules |
| No-prompts switch (containers only) | --dangerously-skip-permissions | Run Everything | --yolo |
Claude Code
1. Check which mode you're in
Press Shift+Tab to cycle modes and watch the status bar: ⏸ manual mode on asks before most edits and commands, ⏵⏵ accept edits on lets file edits through, and ⏵⏵ auto mode on hands the decisions to a classifier. Since v2.1.283, auto mode is the built-in starting mode for interactive terminal and VS Code sessions. If you still start in Manual, one of these is usually why:
- Auto mode isn't available to you: it needs a supported model, and an admin can turn it off with
disableAutoMode. - You set
"defaultMode": "auto"in.claude/settings.jsonor.claude/settings.local.json. Claude Code ignoresautofrom project files. Put it in~/.claude/settings.json. - You're running
claude -p. Non-interactive runs start in Manual mode, and calls that need approval are denied rather than prompted (more on that below).
Auto mode isn't a guarantee: the docs warn that it "reduces permission prompts but does not guarantee safety". If file edits are your main source of prompts, acceptEdits is the smaller step. It approves edits and common filesystem commands (mkdir, touch, rm, rmdir, mv, cp, sed) inside your working directory and leaves other commands alone.
2. Allow the commands you run all day
In .claude/settings.json (committed, for the team) or ~/.claude/settings.json (just you):
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"permissions": {
"allow": [
"Bash(npm run lint)",
"Bash(npm run test *)",
"Bash(npm run build)",
"Bash(npx tsc --noEmit)",
"Bash(git add *)",
"Bash(git commit *)"
],
"ask": [
"Bash(git push *)",
"Bash(npm publish *)"
],
"deny": [
"Read(./.env)",
"Read(./.env.*)"
]
}
}
How the patterns behave, per the permissions docs:
- A rule without
*matches one exact command.Bash(npm run build)doesn't matchnpm run build --watch. - Put the
*after the subcommand, with a space before it.Bash(git commit *)allows only commits;Bash(git *)allows every git command, push included. - Rules are checked deny first, then ask, then allow, and the first match wins. That's why the
askrule forgit pushholds even if a broader allow rule also matches. - In a chain like
npm test && git push, every part has to be allowed. Built-in read-only commands such asls,cat,grep,headand read-onlygitnormally run without a prompt, so they don't need rules. - Runners such as
npx,docker execanddevbox runrun whatever follows. The docs' own example:Bash(devbox run *)also matchesdevbox run rm -rf .. Allow the full command instead (Bash(npx tsc --noEmit)).
Choosing "Yes, and don't ask again" on a Bash prompt saves the rule to .claude/settings.local.json at the repo root, so your list grows on its own. A file-edit approval only lasts until the session ends. Run /permissions to see every rule and the file it came from, and /status to check that a settings file loaded (a trailing comma makes the file invalid).
Allow rules in a project's .claude/settings.json only take effect after you accept the workspace trust dialog, which lists them. Deny and ask rules apply right away, since they only restrict.
3. Or let the sandbox approve commands
Run /sandbox and choose auto-allow. Commands then run inside an OS-level boundary (writes limited to the working directory and a temp folder, network through a proxy that asks the first time a command needs a new domain), and a command that runs inside it doesn't prompt. Deny rules, ask rules like Bash(git push *) and rm of critical paths still go through the normal checks. On Linux and WSL2 the sandbox needs bubblewrap and socat, and if it can't start, Claude Code runs commands unsandboxed unless you set sandbox.failIfUnavailable. The sandbox covers shell commands only, not Claude's file tools, MCP servers or hooks.
4. Scripts and CI
claude -p starts in Manual mode, and there's no one to answer a prompt, so calls that need approval are denied. Pass the commands up front, or choose a mode:
claude -p "run the test suite and fix failures" --allowedTools "Bash(npm test *)" "Edit"
claude -p "run the test suite and fix failures" --permission-mode auto
For locked-down CI, --permission-mode dontAsk denies anything that isn't pre-approved. --dangerously-skip-permissions belongs only in a container or VM, run as a non-root user. In that mode allow rules have no effect, deny rules still block, and explicit ask rules still prompt.
Cursor
1. Pick a Run Mode
In Cursor Settings → Agents → Approvals & Execution you get three modes. Auto-review (the default since 3.6) runs allowlisted calls, runs other shell commands in the sandbox where it can, and sends the rest to a classifier. Allowlist runs only what's on your list, plus supported commands in the sandbox if sandboxing is on. Run Everything runs every tool call. The old "Ask Every Time" mode was deprecated in 3.5; Allowlist with an empty list does the same. Cursor says plainly that Auto-review "is not a security boundary", because the classifier can make mistakes either way.
2. Why an allowlisted command still asks
The allowlist works on command prefixes: git covers git status and git push, while git status covers only that. Matching is case-sensitive. A Cursor team member explained on the forum in September 2026 that the list is "matched per command, not per line", so cd api && npm test | tail -20 runs on its own only if cd, npm and tail are all listed. Their suggestions:
- Open the Always run option on the approval card. It lists exactly which commands weren't on the allowlist.
- Add a User Rule such as "Run one shell command per tool call. Do not chain commands with && or pipes unless the task requires it."
- On a local workspace, use Allowlist with sandboxing on, so commands the sandbox can contain run without being listed.
Some lines still prompt when every command is listed. In another thread (August 2026), Cursor staff confirmed that a command chaining && into a heredoc can prompt, and that matching is literal, so "$HOME/.dotnet/dotnet" and $HOME/.dotnet/dotnet are different entries. Running the commands separately avoids the first; adding the exact string the agent runs fixes the second.
3. Keep the allowlist in a file
.cursor/permissions.json (the file accepts comments):
{
// Replaces the in-app terminal allowlist while this key is set.
"terminalAllowlist": [
"npm run lint",
"npm run test",
"npm run build",
"npx tsc --noEmit",
"git add",
"git commit",
"cd",
"ls",
"head",
"tail",
"grep",
"wc"
],
// Only read in Auto-review mode: steering, not enforcement.
"autoRun": {
"block_instructions": [
"Every git push, npm publish or deploy command should go through approval first."
]
}
}
- When the file defines
terminalAllowlist, it replaces the in-app list for terminal commands, and the in-app editor becomes read-only. An empty array means an empty allowlist, not a fallback to the app's. ~/.cursor/permissions.jsonand the project file are combined, so a repo's entries add to yours. Read the file in a repo you didn't write.autoRun.block_instructionsis read only in Auto-review mode, and the docs call it steering, not enforcement.- MCP tools have their own list,
mcpAllowlist, withserver:toolentries such asgithub:*. Without it, each MCP tool call needs approval.
Some prompts aren't about the allowlist at all. File-Deletion Protection (including rm), External-File Protection (files outside the workspace) and Browser Protection can require approval in any mode. The Cursor CLI has a separate system: Shell(git), Read(...) and Write(...) entries under permissions.allow and permissions.deny in ~/.cursor/cli-config.json or .cursor/cli.json, with deny winning.
Codex
1. Know what the default already allows
Codex's default Auto preset is --sandbox workspace-write --ask-for-approval on-request. Codex reads files, makes edits and runs commands in the workspace without asking. It asks when a command needs the network (off by default) or wants to write outside the workspace. So if Codex keeps asking, the question is usually which boundary the command crosses.
2. Widen the boundary, narrowly
- Network:
network_access = trueunder[sandbox_workspace_write]inconfig.tomllets commands reach the network. That applies to every command, so the docs pair it with thenetwork_proxyfeature and a domain list if you want to restrict where they go. - Extra folders:
writable_rootsunder the same table adds specific directories, such as a scratch folder or a neighbouring repo. - Specific commands: a rules file decides what may run outside the sandbox. Put it in
.codex/rules/default.rulesor~/.codex/rules/default.rules:
# .codex/rules/default.rules (or ~/.codex/rules/default.rules)
# Rules decide what may run OUTSIDE the sandbox. Commands that fit inside it don't ask.
prefix_rule(
pattern = ["npm", "install"],
decision = "allow",
justification = "Installing packages needs the network",
match = ["npm install", "npm install zod"],
not_match = ["npm publish", "npx npm install"],
)
prefix_rule(
pattern = ["gh", "pr", ["view", "checks", "diff"]],
decision = "allow",
justification = "Reading pull requests is fine",
match = ["gh pr view 42", "gh pr checks 42"],
not_match = ["gh pr merge 42", "gh issue view 42"],
)
prefix_rule(
pattern = ["git", "push"],
decision = "prompt",
justification = "Pushes are shared; ask first",
match = ["git push", "git push origin main"],
)
prefix_rule(
pattern = ["git", "reset", "--hard"],
decision = "forbidden",
justification = "Use git stash or a new branch instead of reset --hard",
match = ["git reset --hard HEAD~1"],
)
Each rule matches a command prefix (a list inside the list means "any of these"), and when several rules match, the strictest decision wins: forbidden, then prompt, then allow. The match and not_match lines are tests Codex checks when it loads the file. When we got one wrong, codex execpolicy check refused to load the file. Test a rule before you rely on it:
codex execpolicy check --pretty --rules .codex/rules/default.rules -- npm install zod
Codex splits bash -lc "a && b" into separate commands only when the script is plain words joined by &&, ||, ; or |. Redirects, variables, wildcards or substitutions make it check the whole script as one command, so an allow rule for git add never lets git add . && rm -rf / through. Clicking "allow" in the terminal UI writes a rule to ~/.codex/rules/default.rules, and project rules load only when the project is trusted. Rules are still marked experimental.
3. Let a reviewer answer instead of you
approval_policy = "on-request"
approvals_reviewer = "auto_review"
In the ChatGPT desktop app this is Approve for me. The docs describe it as "a reviewer swap, not a permission grant": the sandbox stays the same, and only requests that already need approval go to the reviewer agent. It uses extra model calls, and in the current implementation it stops the turn after 3 denials in a row or 10 in the last 50 reviews. The docs' advice for a noisy reviewer is to fix the boundary first with writable_roots and precise prefixes like ["cargo", "test"], "over broad patterns such as ["python"] or ["curl"]".
-a never turns prompts off but keeps the sandbox. --yolo (--dangerously-bypass-approvals-and-sandbox) turns off both, and the docs suggest it only inside a container that is itself the boundary. If an old config still says approval_policy = "untrusted", remove it: that value is retired and can stop Codex from starting.
Broad rules that look narrow
| Rule | Why it's broader than it looks | Instead |
|---|---|---|
Bash(git *), Cursor git | Covers git push, reset --hard and clean -f | One rule per subcommand you want: git add, git commit |
python, node, bash -c | An interpreter runs any code you give it | The exact script: python3 manage.py test |
npx, docker run, devbox run | Runs whatever comes after it | The full command: npx tsc --noEmit |
curl, wget | Can send files and secrets anywhere | Leave them prompting, or use the sandbox's domain allowlist |
Bash (no parentheses) | Every command | Use a sandbox or a reviewer mode, plus a guard hook |
If you do allow Bash wholesale, Claude Code's docs suggest pairing it with a PreToolUse hook that rejects the commands you never want, because a hook that exits 2 blocks before allow rules are checked. We built one for all three tools in hooks in Claude Code, Cursor and Codex. Allow rules also don't stop the agent reading secrets with an allowed command like grep; for that, see how to stop Claude Code, Cursor and Codex reading your .env file.
Audit what you've already approved
"Don't ask again" clicks pile up. This script reads the allow rules from all three tools for the current repo and your home folder, and flags the ones that cover a whole program, an interpreter or any command. It exits 1 when it finds something, so it can run in CI or a pre-commit hook. Python 3.11+ only, no dependencies:
#!/usr/bin/env python3
"""List the auto-approve rules Claude Code, Cursor and Codex will use in this repo and flag the broad ones.
Run anywhere in the repo: python3 audit_allowlists.py (exit 1 if anything is flagged)"""
import json, pathlib, re, subprocess, sys, tomllib
git = subprocess.run(["git", "rev-parse", "--show-toplevel"], capture_output=True, text=True)
H, P = pathlib.Path.home(), pathlib.Path(git.stdout.strip() if git.returncode == 0 else ".")
RUNS_ANYTHING = {"bash", "sh", "zsh", "fish", "pwsh", "python", "python3", "node", "deno", "bun", "ruby", "perl",
"php", "eval", "exec", "sudo", "env", "xargs", "npx", "bunx", "uvx", "docker", "ssh", "curl", "wget"}
PUSHES = {"git", "gh", "npm", "pnpm", "yarn", "rm", "kubectl", "terraform", "aws", "gcloud", "az", "vercel"}
flags = []
def check(where, rule, words, wildcard=True):
"""words = the fixed words before any wildcard; wildcard = the rule also covers longer commands."""
if not words:
flags.append(f"{where}: {rule} -> matches any program")
elif not wildcard:
return # an exact command is as narrow as it gets
elif len(words) == 1 and words[0] in RUNS_ANYTHING | PUSHES:
flags.append(f"{where}: {rule} -> covers every '{words[0]}' command")
elif words[0] in RUNS_ANYTHING and words[1] in ("-c", "-lc", "-e", "eval", "exec", "run"):
flags.append(f"{where}: {rule} -> runs any code")
def cursor_words(rule): # "npm:install*" = base command "npm" + args glob "install*"
base, _, args = rule.partition(":")
return base.split() + args.split("*")[0].split()
def load_json(path):
text = re.sub(r"^\s*//.*$", "", path.read_text(), flags=re.M) # Cursor files are JSONC
try:
return json.loads(text)
except ValueError as e:
flags.append(f"{path}: not valid JSON ({e}), so the tool may ignore it")
return {}
for f in (P / ".claude/settings.json", P / ".claude/settings.local.json", H / ".claude/settings.json"):
if not f.is_file(): continue
perms = load_json(f).get("permissions", {})
if perms.get("defaultMode") == "bypassPermissions": flags.append(f"{f}: defaultMode bypassPermissions")
for r in perms.get("allow", []):
m = re.fullmatch(r"Bash(?:\((.*)\))?", r)
if not m: continue
spec = (m.group(1) or "*").replace(":*", " *")
check(f, r, spec.split("*")[0].split(), "*" in spec)
for f in (P / ".cursor/permissions.json", H / ".cursor/permissions.json"):
if f.is_file():
for r in load_json(f).get("terminalAllowlist", []):
check(f, r, cursor_words(r))
for f in (P / ".cursor/cli.json", H / ".cursor/cli-config.json"):
if f.is_file():
for r in load_json(f).get("permissions", {}).get("allow", []):
m = re.fullmatch(r"Shell\((.*)\)", r)
if m: check(f, r, cursor_words(m.group(1)))
for d in (P / ".codex/rules", H / ".codex/rules"):
for f in sorted(d.glob("*.rules")) if d.is_dir() else []:
for block in f.read_text().split("prefix_rule(")[1:]:
pat = re.search(r"pattern\s*=\s*\[(.*?)\]\s*[,)]", block, re.S)
dec = re.search(r'decision\s*=\s*"(\w+)"', block)
if pat and (dec.group(1) if dec else "allow") == "allow":
words = re.findall(r'"([^"]*)"', pat.group(1).split("[")[0])
check(f, f"prefix_rule{words}", words)
for f in (P / ".codex/config.toml", H / ".codex/config.toml"):
if f.is_file():
try:
cfg = tomllib.loads(f.read_text())
except tomllib.TOMLDecodeError as e:
cfg = {}; flags.append(f"{f}: not valid TOML ({e})")
if cfg.get("sandbox_mode") == "danger-full-access" and cfg.get("approval_policy") == "never":
flags.append(f"{f}: no sandbox and no approvals")
print("\n".join(flags) or "No broad auto-approve rules found.")
sys.exit(1 if flags else 0)
It's a pattern check, not a parser for each tool's full syntax, and it doesn't look at managed or team settings. Edit RUNS_ANYTHING and PUSHES for your stack. We tested it on sample repos: a narrow config like the ones above (no flags), a deliberately broad one with 16 risky entries across the three tools (all 16 flagged), rules in a home folder, a run from a subfolder, and broken JSON and TOML files. We tested the Codex rules file with the Codex CLI's own execpolicy check. The Claude Code and Cursor files were checked as valid JSON but not loaded in those tools.
A fifteen-minute setup
- Pick the middle-ground mode: auto mode or
/sandboxauto-allow in Claude Code, Auto-review in Cursor, the default Auto preset in Codex. - Write down the five to ten commands the agent runs every session (test, lint, typecheck, build,
git add,git commit) and add them as exact or subcommand-level rules. - Add an ask/prompt rule for
git pushand your publish or deploy command. Claude Code ask rules prompt even in auto mode. - Commit the project files (
.claude/settings.json,.cursor/permissions.json,.codex/rules/) so the team and every worktree start the same way, and keep personal extras in your home folder. - Run the audit script, then work for a day and add rules only for prompts that come back.
FAQ
How do I stop Claude Code asking for permission every time?
Add allow rules for the commands you run all the time, such as "Bash(npm run test *)", to .claude/settings.json or ~/.claude/settings.json, and use acceptEdits mode if file edits are what keeps asking. For fewer prompts across the board, use auto mode (a classifier reviews actions instead of you) or turn on the Bash sandbox with /sandbox and pick auto-allow. Keep an ask rule for git push: ask rules prompt in every mode.
Is --dangerously-skip-permissions safe?
Only in an isolated container or VM. It starts Claude Code in bypassPermissions mode, which skips prompts except for a short list of actions no mode auto-approves. Deny rules still block, but allow rules stop mattering because everything else runs. Codex's equivalent, --dangerously-bypass-approvals-and-sandbox (alias --yolo), turns off both the sandbox and approvals.
Why does Cursor still ask for a command that's on my allowlist?
The allowlist is matched per command, not per line, so every command in a chain has to be on it: cd api && npm test | tail -20 needs cd, npm and tail. Cursor staff also confirmed in August 2026 that a heredoc chained after && can still prompt. Open the Always run option on the approval card to see which commands are missing, or ask the agent to run one command per call.
Why does Codex ask before npm install or curl?
Codex's default Auto preset runs commands inside a workspace-write sandbox with no network access, and asks before anything that needs the network or writes outside the workspace. Turn on network_access under [sandbox_workspace_write] if your work needs it, or add a narrow prefix_rule in .codex/rules that allows a specific command outside the sandbox.
Can a repo I clone pre-approve commands for me?
In Claude Code, allow rules in a project's .claude/settings.json apply only after you accept the workspace trust dialog, which lists them. Codex loads a project's .codex/rules only when the project is trusted. Cursor concatenates a repo's .cursor/permissions.json allowlist with yours, and its docs don't describe a trust step for that file, so read it before you work in an unfamiliar repo.
Sources
- Claude Code docs: Configure permissions (allow / ask / deny rules, wildcards, workspace trust)
- Claude Code docs: Choose a permission mode (Manual, acceptEdits, auto, dontAsk, bypassPermissions)
- Claude Code docs: Configure the sandboxed Bash tool (auto-allow mode)
- Claude Code docs: Settings files and precedence
- Cursor docs: Run Modes (Auto-review, Allowlist, Run Everything, sandbox)
- Cursor docs: permissions.json reference (terminalAllowlist, mcpAllowlist, autoRun)
- Cursor docs: Agent security
- Cursor docs: CLI permissions
- Codex docs (ChatGPT Learn): Agent approvals & security
- Codex docs (ChatGPT Learn): Rules (prefix_rule, execpolicy check)
- Codex docs (ChatGPT Learn): Auto-review