Book a call
RECIPE16mVERIFIED 2026-08-04 · CLAUDE CODE 2.1.221 · CODEX CLI 0.146.0 · ANTIGRAVITY CLI 1.1.9 · KIMI CODE CLI 0.31.1

Permission fatigue and the middle path

The procedure for making an agent stop asking about everything without turning approvals off: count what you actually approve, promote on the second one, and prove the rule matches.

The two ways people usually react to this, both flawed

What we did is we basically stopped reading these by the second week because we were approving them dozens of times per day. But it's not about discipline, once you think of it it's obvious it's not actually a decision at all. You can't make a decision on something that's always been decided weeks ago, and you can't decide to let it through if you weren't actually presented with the information. What happens here is that the system charges your attention for things that are already decided.

So what people do in such situations is either of these two. The first option is "well I know I should be approving these, but I don't want to see them so I'll just approve anyway" — then you still have a rubber stamp that's pointless once you stopped reading and you add the delay. The second option is "I know I'm supposed to approve these so I'll turn it off" — then the same things happen but you're not even aware of it, including the few per week you'd actually stop. What we should do instead is deliberately make ourselves ignorant about these to create an empty queue for the rare ones that actually come and get read.

It's a one-off setup process (though might require occasional updates), not a single switch. The whole thing takes about half an hour the first time.

Measure what you actually approve, don't build the list in your head

Don't try to build the allow list in your head, you'll forget some things and will include others that you actually rarely run. Instead we need to measure what we actually approve.

Some of the tools keep a tally of what the agent did — session transcripts. It's a superset of what the system prompts us about as it includes the calls that were auto-approved too, but that's okay as these are all based on the same distribution. We also sometimes have access to what we've already approved — the file the tool appends when you choose "Don't ask again". That's a much more faithful sample as every line there is something you actually got tired of, but not all tools keep it. It's in Claude Code and Codex, for the other three we'll use the per-tool instructions below.

For every tool list what it runs in the terminal, not the entire command line (so cd instead of cd /some/dir).

On our machine in the last two weeks these were: grep, echo, cd, ls, sed, python3, cat, with cargo and git further down. What's important though is that destructive operations weren't near the top. If you look at this you can come up with two conclusions (the second one people usually miss):

  • The obvious one — most of these are the agent examining things.

  • The one people often miss — this isn't an allow list. cd isn't examination, sed can modify stuff, python3 is number six and it's running arbitrary code which you'd never allow just like that, and in fact its high position is a piece of information too.

So treat it as a map of where your attention goes and then examine every rule — the first thing to check is if the tool isn't already treating sth as read-only for free. In Claude Code there's a set of commands (ls, cat, grep, wc, find, plus another dozen) that are classified as read-only so it doesn't ask about them in any mode, so an allow rule for these buys you nothing and you can remove half of your list on this basis alone.

If you were to actually count the entire command lines it'd take a day, for example cd /some/absolute/path will be at the top in such scenario.

Make the rule work after the second approval, not the first

We should have a rule for a command once we approved it twice. The reason is that the first time you approve smth the prompt isn't really informative as lots of commands appear only once. Once you see it the second time you know it's a habit, and habits are the entirety of what we aim to achieve with this.

It also keeps the list small enough for someone new to be able to read it if they join the project, because a list that nobody understands gets wiped on the first occasion anyway.

Two of these five tools hand you the second time for free — Claude Code writes the rule when you pick "Yes, don't ask again", Codex writes one when you add a command to the allow list in its TUI. Use that, then go and read what it wrote.

Make the rule stricter than you want

If you define it too broadly it's all pointless. There are three patterns that cover nearly all of it, and these appear in different tools:

  • The runner — devbox run, npx, docker exec, mise exec, direnv exec etc. These are tools whose purpose is running sth else, so if you allow them you allow everything they can run. This is what the Claude Code docs say: a rule like Bash(devbox run *) matches devbox run rm -rf .. You should name the inner command instead and create separate rules for every actual thing you want to do.

  • The wildcard in the stricter list — in Claude Code the order is deny, ask, allow. They explicitly say that specificity doesn't change it. It's the same in Antigravity. If you have a wildcard covering all commands in your ask list and git in your allow list, you'll be prompted for every git command as ask is above allow. Don't put a wildcard in the stricter list and then try to carve things out of it.

  • The missing whitespace before the wildcard — this is just string matching, in Claude Code for example Bash(ls *) matches ls -la but not lsof, while Bash(ls*) matches both — one space of difference.

(Before porting the rules to another tool check which of these it uses: Codex goes with the most restrictive rule if there are multiple matches, Kimi with the first match in file order, Cursor doesn't have deny or ask arrays at all, only allowlists and a classifier.)

And the fourth thing that comes up later: allowing a tool doesn't tell the system what files it reads, so paths are there to protect the tool's own file utils, not any process that opens the file. That's the role of the sandbox. The Academy's three-gates lesson shows the gap, read it before relying on a path rule.

Make sure the rule actually works as you intended

This is the step people almost always skip and then think allow lists are pointless:

  • If there's a typo in the rule it'll do nothing and won't ever match so you'll keep being prompted about what you thought you've already allowed, with the only indication being a startup warning that you didn't scroll back to see.

  • If the rule is too broad it often doesn't complain at all, you just don't notice it allows more than you intended until later. The only tool that tries to help here is Codex which includes example runs in the rules so the load fails when the pattern is wrong.

Three of these tools have a way to check it without using the LLM. For the other two you'll need to read the file and see how the session goes, we've included the per-tool instructions below.

In any case you should check two things:

  • The thing you actually wanted to allow — it should now be allowed.

  • A neighbouring thing that you didn't want to allow — it shouldn't be allowed.

It's this second test that actually surfaces mistakes.

IN YOUR HARNESS

In Claude Code

Where the evidence is.

If you choose "Yes, don't ask again" on a Bash prompt, the rule lands in .claude/settings.local.json at the root of the Git repository, and it then applies to all future sessions across the whole repo, subdirectories and worktrees included. File edits work differently — those approvals only last until the session ends. You can always run eg this command to retrieve the entries from this file:

BASH
jq -r '.permissions.allow[]?' .claude/settings.local.json

This is your chance to enforce some good habits that you'd like to be part of your workflow, but also to be selective about it, as the same file will collect Read rules automatically generated by Claude for every directory you enter via cd. What's more, a single compound command can leave up to 5 new entries there.

If you'd like to examine the entire landscape of your habits, you can find the transcripts of all your sessions in ~/.claude/projects/<encoded-path>/*.jsonl. For example, you could run this pipeline to display the 20 most frequent first words of Bash commands from the last 14 days:

BASH
find ~/.claude/projects -name '*.jsonl' -mtime -14 -print0 \
  | xargs -0 -n1 jq -r 'select(.message.content?) | .message.content[]?
      | select(.type=="tool_use" and .name=="Bash")
      | .input.command | split("\n")[0]' 2>/dev/null \
  | sed -E 's/^(cd [^;&]+ *(;|&&) *)+//; s/^([A-Za-z_][A-Za-z0-9_]*=[^ ]* )+//' \
  | awk '{print $1}' | sort | uniq -c | sort -rn | head -20

The thing is, there are a few details here that require some attention:

  • We need to use find with -print0 piped into xargs -0, because the project directory names have / replaced with -, so anything glob-based hands ls an argument starting with a dash and it dies

  • We need to output only the first line of every command to not end up with a top-20 dominated by echo "===, EOF, } and done — these are the most common lines of heredocs. The same applies to the second sed expression that removes leading environment-variable assignments so we don't have to see NODE_ENV=test in the results when what actually ran was NODE_ENV=test npm test

Before you write a rule.

Before you define a new rule, make sure that you're not trying to allow one of these built-in Bash commands as they are all treated as read-only and run without asking in all modes: ls, cat, echo, pwd, head, tail, grep, find, wc, which, diff, stat, du, cd, and git in its read-only variants. Note that this list is hardcoded and can't be altered; if you create an allow rule for any of these commands it won't do anything. The only exception is find, sort and sed as well as git — they all still ask if there's an unquoted glob in the command, as it could expand into a flag like -delete.

Writing the rule.

When it comes to the syntax, these are under permissions.allow / permissions.ask / permissions.deny arrays of .claude/settings.json:

  • The space before * marks a word boundary; the colon is just a syntactic sugar, so for example Bash(ls:*) works exactly the same way as Bash(ls *) — but that form is only recognised at the end of a pattern

  • We strip a set of wrapper commands from the command before comparing it with the provided pattern. It means that Bash(npm test *) will also match timeout 30 npm test for example, as timeout is stripped before comparing. Other than that, we also strip time, nice, nohup and stdbuf, as well as command, builtin, zsh's noglob or plain xargs

We intentionally haven't included environment runners such as devbox run or npx in this list, for the reasons explained in step 3.

Proving it (two checks, neither costs a model call).

There are two ways to verify if your configuration is valid that don't require an LLM call:

  • You can run claude doctor and it will tell you if the JSON is broken or if there are any allow rules that were discarded

TEXT
Invalid settings
- /path/to/.claude/settings.json › permissions.allow: Invalid permission rule "B*" was skipped:
  Wildcard tool name "B*" is not supported in allow rules. An allow pattern must name the scope
  it widens — globs are permitted only in the tool position after a literal mcp__<server>__ prefix.
  Deny and ask rules accept wildcards anywhere
  • Or you can run claude with a prompt set to nothing via --settings and it will parse the settings, print all the per-rule warnings (if any) and exit with an error due to lack of input before sending any request. For example you could do:

BASH
claude --settings '{"permissions":{"allow":["Write(docs/**)","Glob(docs/**)"]}}' -p ""

— this will print the following warnings:

TEXT
Permission allow rule (...): Write(docs/**) is not matched by file permission checks — only
  Edit(path) rules are. Use Edit(docs/**) instead (Edit rules cover all file-editing tools).
Permission allow rule (...): Glob(docs/**) is not matched by file permission checks — only
  Read(path) rules are. Use Read(docs/**) instead (Read rules cover all file-reading tools).
Error: Input must be provided either through stdin or as a prompt argument when using --print

As you can see from these examples, the paths are being interpreted only as Edit(path) or Read(path), so if you were to use Write or Glob or NotebookEdit instead, they'd be accepted but would never be used. These rules do emit a warning at the beginning of every session — which is exactly why nobody sees it, and why this run is worth doing. Also, keep in mind that these are two different types of checks — on 2.1.221 for example, claude doctor tells you about the discarded wildcard rules but doesn't say anything about the ineffective path-based rules, so make sure to use both.

We've also noticed that in the documentation it's written that Bash(command:rm *)-like rules are being discarded with a startup warning, but on 2.1.221 it seems like this warning is actually being displayed only for deny rules and not for allow ones, so if you see no warning for your allow rule that doesn't mean it works.

The lever.

The most significant improvement you can make in terms of reducing the number of interruptions is to turn on the sandbox and leave autoAllowBashIfSandboxed as is. In that scenario, a simple Bash ask rule stops being a universal "ask about everything" and becomes "ask only about what I've specified" instead — that's the line between a configuration that minimises the number of interruptions and a configuration that just moves them around.

In Codex CLI

Where the evidence is. Codex already keeps the list for you. The docs put it plainly: "When you add a command to the allow list in the TUI, Codex writes to the user layer at ~/.codex/rules/default.rules so future runs can skip the prompt." If you open that file, you'll see your already-approved commands converted into rules:

BASH
cat ~/.codex/rules/default.rules

Every time you start Codex, it loads files under rules/ from every layer that's active in the current config. The user layer is ~/.codex/rules/, and there's also a project layer for each repo at <repo>/.codex/rules/, loaded only if the project's .codex layer is trusted.

Writing the rule. All these files are using the Starlark language, with the prefix_rule function as a building block. Every rule has a decision attribute which can be one of: allow, prompt, forbidden. When multiple rules apply, the strictest one is being used (allowing is less strict than prompting, and prompting is less strict than forbidding). So this is how a basic allow rule might look like:

PYTHON
prefix_rule(
    pattern = ["cargo", "test"],
    decision = "allow",
    justification = "Test runs are read-only for the repo and safe to auto-run.",
    match = ["cargo test", "cargo test --lib"],
    not_match = ["cargo publish"],
)

Proving it. This will allow running cargo test in future, based on the reason and some examples of matching and non-matching invocations. The match and not_match fields are basically unit tests integrated with the rule. For every loaded rule Codex runs them and verifies if they pass, so essentially verification (point 4) is part of authoring (point 3), and you need to remember about it. If you want to exclude a neighbouring command from the whitelist, you can list it under not_match, but make sure that the pattern isn't too wide because then the entire file will not be loaded by Codex:

TEXT
Error: failed to parse policy at /path/to/bad.rules

Caused by:
    expected example to not match rule `PrefixRuleMatch { matched_prefix: ["git"],
    decision: Allow, resolved_program: None, justification: Some("all git") }`: git push origin main

As you can see, in such situation Codex throws an error. But if you ever wonder what's the result of evaluating a particular command against a set of rules, you can use the codex execpolicy check tool which will evaluate it locally:

BASH
codex execpolicy check --pretty --rules ~/.codex/rules/default.rules -- cargo test --lib

This is the result:

JSON
{
  "matchedRules": [
    {
      "prefixRuleMatch": {
        "matchedPrefix": ["cargo", "test"],
        "decision": "allow",
        "justification": "Test runs are read-only for the repo and safe to auto-run."
      }
    }
  ],
  "decision": "allow"
}

As you can see, in this case only one rule was matched, and it allowed this command to be run. But it's worth knowing what a "no rule matched" result looks like, because it's different:

JSON
{
  "matchedRules": []
}

The no-match result is a bit weird, it doesn't contain the decision key at all and returns an empty list of matched rules instead. Anyway, if a rules file can't be loaded, codex execpolicy check will return with exit code 1, otherwise 0, so you can easily use it in your CI pipeline without any modifications.

Compound commands. The last thing we wanted to mention is that when it comes to chained commands, Codex does split them if they're connected using a plain operator — &&, ||, ; or |. So for instance if you run the following:

BASH
codex execpolicy check --pretty --rules rules.rules -- bash -lc 'cargo test && rm -rf /tmp/x'

Codex will evaluate each segment separately, so an allow rule can't be used as a carrier. It comes back {"matchedRules": []} even with cargo test allowed.

There's one thing we should mention though: if your command contains any substitution (like $(...)) or uses redirection, variable expansion or wildcard characters, the splitting will not work. In such scenario, the entire script will be evaluated as one bash -lc call and therefore your prefix rules stop applying. That's conservative, which is the right direction — but it does mean the neat prefix rules you just wrote quietly stop covering anything with a $(...) in it.

The gotcha. Last thing: by default Codex has Smart approvals enabled, which is a really useful feature and lets it propose prefix_rule entries during escalations. As you can see, that's really the fastest way to implement point 2 — but make sure you always have a look at what Codex suggests before you confirm anything, because accepting a wider prefix than you meant is exactly how the list rots. And remember that for now all rules are experimental, so it's good to pin the version if you want to use any feature based on them.

In Cursor

Finding the evidence. If you have a look at the permissions docs, nothing there describes a file of promoted rules or a transcript you could count. As a fall-back we could consider the existing allowlist as an approval record — both terminalAllowlist in permissions.json and entries added in the settings UI. In that case we can prune from there.

Mechanics. It's important to know that there are two files being merged, not one overriding another:

  • ~/.cursor/permissions.json — user level, applies globally

  • <workspace>/.cursor/permissions.json — repo level

Defining rules. There are three top-level keys in the config, each with a different meaning:

JSON
{
  "terminalAllowlist": ["git status", "git diff", "cargo test", "npm:install*"],
  "mcpAllowlist": ["my-server:list_*"],
  "autoRun": {
    "allow_instructions": ["Read-only inspections of build artifacts under ./dist are fine."],
    "block_instructions": ["Always pause delete operations so I get a chance to review them."]
  }
}

terminalAllowlist: matches by prefix, case-sensitive, and allows globbing of arguments using a colon to separate the base command. So git matches anything starting with git, git status matches only that one, and the more characters you provide the more specific the match becomes.

mcpAllowlist: uses server:tool, case-insensitive, allows for globbing on both sides.

autoRun: a little different from the above two, these are plain sentences in English to help guide the Auto-review classifier. It's good to come up with such sentences as you would communicate your thoughts to a colleague — what they should look out for.

Important: as the docs say, a call matching allow_instructions still goes through the safety check and a call matching block_instructions can still end up approved — both are "steering, not enforcement". If you want something to actually hold, use terminalAllowlist for that; autoRun is more of a general guideline.

Verification. There's actually no tool to do a local validation, it's all about having a look. What's important in terms of precedence is that team admin settings > permissions.json > IDE settings UI, and also if there's sth in the permissions.json file it completely replaces the respective allowlist in the IDE.

If you were to remove smth from the IDE allowlist and it still gets auto-run — it means the permissions.json file isn't being used.

The gotcha. This one can be really tricky: permissions.json only takes effect when Run Mode is enabled, and the autoRun instructions are only consulted in Auto-review mode.

  • Allowlist mode — allowlists are on and the natural language guidelines from autoRun are not used

  • Run Everything — both of the above are ignored

And nothing errors when this happens. A file that looks like a working config, plus a mode that ignores it, fails completely silently.

CLI notes.

  • -f / --force is the equivalent of Run Everything. Its help text says "Force allow commands unless explicitly denied" — a narrower promise than similar flags in other tools make, though permissions.json documents no deny list at all, so make sure to check the reference before relying on that

  • --auto-review explicitly picks the classifier path

  • --sandbox enabled turns on containment regardless of the config

In Antigravity CLI

The name of the Antigravity binary is agy. Just wanted to point out that in the Gemini CLI guides there's information about a TOML permissions layer which belongs to Gemini CLI, not to this tool — Antigravity uses JSON, in a separate file, and none of that config carries over.

Where the evidence is. Let's find the record: there's no file with the promoted rules, so we can't list them, and there's no session log to count either. What's left is the configuration itself — the permissions section of ~/.gemini/antigravity-cli/settings.json. Would you personally still add every rule that's already there?

Writing the rule. The Antigravity tool stores all its configuration in a single file, under the permissions key, where there are three arrays: allow, deny, ask. The syntax of a rule is an action with a target wrapped inside it:

JSON
{
  "permissions": {
    "allow": [
      "command(git (status|diff|log))",
      "command(npm run (build|lint|test))"
    ],
    "deny": [
      "command(rm -rf)",
      "command(sudo)",
      "write_file(/home/user/.ssh)"
    ]
  }
}

There are a few more action types available, listed in the docs: read_file, write_file, read_url, execute_url, command, unsandboxed and mcp.

The Antigravity tool's command matching works per token — every word gets treated as an anchored regex, thanks to which we can use alternation to narrow it down to the three subcommands of git instead of every action starting with git. I'd say that using a per-word regex is the most precise way of limiting things in this regard, so it's good to aim for that rather than settling for a prefix match.

There are also two implications in place by default, which point to what seems logical and eliminate the need to create a separate entry for the opposite action:

  • If you set write_file permission on some path, you automatically get read_file permission on it

  • If you set deny for read_file on some path, you can't write there too

Proving it. There's no doctor command and there's no validator that checks the configuration file for this tool, so step 4 here means inspecting the configuration rather than executing anything, the same position Cursor is in. It makes more sense to focus on the precedence though, as this is where unexpected behaviour comes from — the rules are prioritised like that: Deny above Ask above Allow.

For example if you were to create an ask rule that uses a wildcard for the command and then add an allow record with a more narrow git expression, every time you run any git command you'll be asked, because ask outranks allow. If you have allow records which seem to be ignored — check whether there's a wildcard sitting in the deny or ask array.

There are some things that you don't need to configure:

  • Read and write operations inside your project folder are allowed by default

  • Everything else that you don't specify falls back to ask, including web actions

The lever. The middle option is a feature of the Antigravity tool itself, available as an option in the configuration file. The toolPermission property is set to "request-review" by default and the docs say that one prompts for write, bash and web tools, while "proceed-in-sandbox" means it auto-proceeds inside the sandbox. There's also another setting called enableTerminalSandbox, which is set to false by default:

JSON
{
  "toolPermission": "proceed-in-sandbox",
  "enableTerminalSandbox": true
}

You either enable both or disable both of them, as proceeding in a sandbox requires there to be a sandbox.

The gotcha. The last thing is the trap — before you confirm in the prompt that asks if you want to run the tool, you can edit the target's scope and widen it. That's very convenient but at the same time it's how you end up with too many allow records — it's like doing step 2 without doing step 3. So if you decide to edit anything there, make sure it doesn't widen the path.

In Kimi Code CLI

Sourcing the evidence — in terms of how to find what you've been approving, the docs only ever describe hand-written configs; there's no file of pre-approved rules to read back, so this step has to be done by hand for now. Either you observe the prompts for a week, or you look at the existing rules and remove the ones you wouldn't create again.

The syntax of the rules — they're defined in ~/.kimi-code/config.toml in an array of tables under [[permission.rules]]. They are as follows:

  • decision: allow, deny, or ask

  • pattern: tool name or tool name with an argument

  • optional scope

  • optional free-text reason

Here's a sample config with the default manual mode set, a deny for destructive removal, general allows for read and tests, and a catch-all ask on Bash at the end:

TOML
default_permission_mode = "manual"

[[permission.rules]]
decision = "deny"
pattern = "Bash(rm -rf*)"
reason = "Destructive; use git clean."

[[permission.rules]]
decision = "allow"
pattern = "Read"

[[permission.rules]]
decision = "allow"
pattern = "Bash(cargo test*)"

[[permission.rules]]
decision = "ask"
pattern = "Bash"

The most important thing to understand is that the order of rules is what actually defines the policy. It's evaluated top-down and the first match applies. No special precedence of deny over ask over allow like in Claude Code or Antigravity. You can use more specific rules on top and the general ask at the end. If you were to place that last ask on Bash at the beginning, it would override the deny for destructive removal below it — and then rm -rf would get you a prompt instead of a refusal.

What's more, I wouldn't say the argument-matching mechanism is perfect here. It does well for Bash, where the argument is the command itself, so Bash(cargo test*) matches what you'd expect. Read works the same way with paths. But for MCP tools, custom tools and AgentSwarm you can't key it to anything but the tool name — you either allow or deny the whole thing, there's no in-between.

Let's move on to verification. There's a CLI command that comes with the tool called kimi doctor which you can point to a path. So we can create an example config and check if it's valid without modifying the live one:

BASH
kimi doctor config ~/.kimi-code/config.toml
TEXT
Kimi doctor found 1 issue.

ERROR config.toml  /path/to/config.toml
  Invalid configuration in /path/to/config.toml.
  Validation issues:
    permission.rules[0].decision: Invalid option: expected one of "allow"|"deny"|"ask"

As you can see it's returning exit code 1 if sth is wrong, and 0 if everything is good, so you could use it as a pre-commit hook if you were to have your rules in version control.

It validates the structure of the file but not the ordering — for example if you create a file which due to its order becomes pointless, it will still pass. Ordering is the mistake I'd expect you to make here, so check that part by reading the file top to bottom; doctor won't do it for you.

The catch is, actually, that there's no OS-level sandbox documented for this harness. No Seatbelt profile, no bubblewrap, no container option — an empty cell in the Academy's reference table for this harness. So we can't do step 6 for now, which means we're fully reliant on the first two layers.

Keep the deny rules genuinely tight then, and consider using a container or VM as the missing third layer whenever you're doing sth unusual.

Also, if you run kimi -p, the tool will automatically run in auto mode so there will be no prompts at all and only your deny rules will remain as a guard. The following flags are available here:

  • --yolo — auto-approves every call to tools but can still ask questions

  • --auto — fully autonomous

  • if you try to combine them, the CLI won't start

Neither of these is useful on your machine though.

Keep a short list of actions that exit your machine, destroy sth or cost money

After the first five steps what's left is a short list in a specific shape. These are things that exit your machine (push, merge, publish, deploy, release), things that destroy sth (force-push, rm -rf, dropping or migrating a database), and things that cost money (anything that spends, anything pointed at production). The same for reading credentials.

Keep it short enough to fit in your head. A ten-line list you know is better than a forty-line one you don't.

Ask, not deny

What we want to achieve is having a short list of things the system always asks about before doing them. This way we keep all our habits at bay, so it's important to understand the difference between ask and deny:

Deny is for what should never happen. Ask is for what should sometimes happen if you're watching.

So put the commands that face outside in the ask list yourself instead of relying on an absence of a rule for them. In Claude Code for example a content-scoped ask rule like Bash(git push *) will still fire even under sandboxing or bypassPermissions, which it wouldn't if it was just not in the allow list.

This also covers for your deny rules when they're too literal. A deny on Bash(git push --force *) won't match git push origin main --force because the flag moved, but a broad ask on Bash(git push *) catches it, so the worst case is a prompt rather than a silent force-push. Layer them that way round on purpose.

The sandbox

This is the biggest lever, and it's disabled by default in more than one tool:

Prompts exist to ask questions. If the answer is already determined by something else than your attention the question becomes cheap enough to omit.

In Claude Code with the sandbox on and autoAllowBashIfSandboxed set to its default of true, sandboxed Bash commands are run without prompts even if you have a bare Bash ask rule in the permissions (the boundary replaces this prompt). Content-scoped ask rules and explicit deny rules still apply though.

In Antigravity it's the same thing but as a named setting. The toolPermission can be set to "proceed-in-sandbox" (which they document as "auto-proceed inside sandbox"), and its enableTerminalSandbox is false by default, so that's the one you have to switch on yourself.

This is the middle path distilled into a single setting — "ask about everything" becomes "ask about what I've listed", backed by an actual boundary instead of string matching.

The Academy's three-gates lesson covers the setup for each tool, this step is here to remind you that the sandbox lowers fatigue, not only risk.

Why not just bypass permissions?

Yes, there are flags: --dangerously-skip-permissions, --yolo, --force, danger-full-access. They're all very transparently named, so it's natural to think of them next.

But they don't actually mean the same thing, so don't assume you know what one of them does based on another. For example in Cursor --force says "Force allow commands unless explicitly denied" in its own help, which is a narrower promise than the others make, but then if you look at their permissions.json reference you see they don't say what's considered explicitly denied so check it before using it.

In Claude Code bypassPermissions still respects explicit ask rules and still prompts on deletions aimed at / or your home dir as a circuit breaker.

In Kimi there are two similarly-named flags: --yolo which auto-approves tool calls if the agent can still ask, and --auto which is fully autonomous. You can't use them together.

(What these flags don't say is that bypassing permissions doesn't decrease the number of potentially harmful actions, it decreases the number you see. The rate comes from what you asked for and what the LLM chose. The flag just removes the place where you'd catch it.)

So they're useful if you have a container or VM you're okay to destroy, where the blast radius is contained by sth else other than your attention.

On your machine with your SSH keys though it's not the middle path, it's the opposite end of the lever.

What this doesn't get you

A quieter prompt stream isn't a security boundary, and vendors say so too — the Cursor reference labels permissions.json "best-effort convenience" rather than a guarantee, and in a separate section they say auto-review isn't a security boundary either. Allow lists are a comfort layer on top of the actual mechanism.

The rules only work if they were loaded, and more than one tool has ways to silently ignore your config. In Claude Code and Codex the project needs to be trusted for its rules to take effect. In Cursor the team-admin settings override your file; there's an Academy lesson about foundations that covers this, it's worth reading before using today's work.

There'll still be prompts. For things that weren't decided yet. That's how it works, not how it breaks.

Whenever you see a prompt for sth for the second time, you're at step two again — the entire maintenance cost.

THE FILE.claude/settings.json
JSON
{
  "permissions": {
    "deny": [
      "Read(.env)",
      "Read(.env.*)",
      "Read(**/*.pem)",
      "Read(~/.ssh/**)",
      "Read(~/.aws/**)",
      "Bash(git push --force *)",
      "Bash(git push -f *)"
    ],
    "ask": [
      "Bash(git push *)",
      "Bash(gh pr merge *)",
      "Bash(npm publish *)",
      "Bash(cargo publish *)",
      "Bash(docker push *)",
      "Bash(terraform apply *)"
    ],
    "allow": [
      "Bash(rg *)",
      "Bash(npm run test *)",
      "Bash(npm run lint *)",
      "Bash(npm run build *)",
      "Bash(cargo check *)",
      "Bash(cargo test *)",
      "Bash(cargo clippy *)"
    ]
  },
  "sandbox": {
    "enabled": true
  }
}
j / k to move between lessons