---
title: "AI Agent Permissions: How to Stop Your Agent Doing Something You Can''t Undo"
description: "Our deny rule stopped one spelling of a branch delete and missed the next. The guards that hold for agent actions you can't undo, and how to test yours."
author: "Harry Floyd"
publication: "The Durability Curve"
canonical: "https://durabilitycurve.com/blog/ai-agent-permissions/"
date: "2026-10-23"
series: "ENGINEERING GUIDES"
claims: "https://durabilitycurve.com/claims/ai-agent-permissions/"
format: "markdown mirror of the canonical HTML page; figures are named, not embedded"
---

# AI Agent Permissions: How to Stop Your Agent Doing Something You Can''t Undo

*Our deny rule stopped one spelling of a branch delete and missed the next. The guards that hold for agent actions you can't undo, and how to test yours.*

By Harry Floyd · 2026-10-23 · canonical: https://durabilitycurve.com/blog/ai-agent-permissions/

*Our deny rule stopped one spelling of a branch delete and missed the next. The guards that hold for agent actions you can't undo, and how to test yours.*

[Figure: A Git branch marked "deletion prohibited", tried against five guards: a prompt, a reviewer model, a sandbox, a deny rule and the server.]

**AI agent permissions are the rules that decide what a coding agent may do without asking you first; a sandbox decides what it can reach when it does.** Claude Code, Codex and Cursor all have both, under different names.

A user asked an agent to "clean up old branches." The agent "listed remote branches, constructed a pattern match, and issued a delete."

The case comes from Anthropic's internal log of times its agents went further than asked, adapted in its [write-up of Claude Code's auto mode](https://www.anthropic.com/research/claude-code-auto-mode). Anthropic calls this overeager: the agent is trying to help and "takes initiative beyond what the user would approve." The user "may have only meant to delete local branches."

Had the agent asked first, a prompt would have shown you the delete, and stopping it would have depended on you reading it. Someone has to judge what "clean up" covered: the agent, you at the prompt, or in auto mode a second model reading the transcript.

This guide follows that one deletion through five guards, from the prompt to a server that refuses it, and ends with a way to check which of your own guards hold.

Short of time? Section 2 carries the argument, and the box in section 3 is the first thing to do.

## Where a guard can sit

An action can be stopped in three places.

**Someone can judge whether you meant it**: you at a prompt, or a reviewer model.

**The agent's machine can block it**, with a rule that names the command, a hook, or a sandbox that limits what commands reach.

**The system the action lands on can refuse it**, which for a branch is the git server.

With nothing set up on the machine or the server, the deleted branch meets only the first kind: a git server accepts a delete unless someone has told it not to.

None of the four stops in [the first guide in this series](https://durabilitycurve.com/blog/what-is-harness-engineering/) sits at the destination, and section 4 shows why that is the place to add one.

## Why asking can't carry what you can't undo

In Manual mode the prompt is the guard you have, and Anthropic has measured how it goes: "approval fatigue, where people stop paying close attention to what they're approving," and "in practice users accept 93% of them anyway."

That rate is what you would expect if most prompts are harmless. The harmful one arrives looking like the rest.

Auto mode hands the judgement to a classifier, a second model that checks each risky action before it runs. Anthropic tested it on 52 real overeager actions, collected from sessions where an employee denied an action or it was flagged afterwards, and the full system let 17% through. On 52 cases, each miss moves that figure by about 2 points.

"The 17% false-negative rate on real overeager actions is the honest number," the write-up says, and it explains what went wrong: "In the majority of misses we examined, the classifier correctly identifies the danger." What it misjudges "is whether a real consent signal in the session actually covers this action." Its example: "'Clean up the PR' was said by the user, but it doesn't name force-pushing."

It is also a chosen trade-off: the second stage "cleans up the false positives but lets a few more dangerous actions through," and the full system wrongly blocked 0.4% of 10,000 real staff actions.

Anthropic's verdict is that against careful approval of every action "it's arguably a regression," and that auto mode is "not a drop-in replacement for careful human review on high-stakes infrastructure."

The agent and the classifier fail at the same judgement, whether this request covers this action, and a prompt asks you to make that judgement again every time, at speed.

So sort your agent's actions by whether you can undo them. The ones you can undo, such as an edit inside a repository with its history, can run with fewer prompts inside a wall.

**Actions you can't undo need a guard that never has to decide what anyone meant.**

## List what your agent can't undo

The block rules Anthropic described when it launched auto mode in March make a ready starting list. The first group is "Destroy or exfiltrate. Cause irreversible loss by force-pushing over history, mass-deleting cloud storage, or sending internal data externally."

Another covers shared systems: "pushing directly to main, running production deploys, or modifying shared resources or jobs the agent didn't create." A third includes "modifying the agent's own permission config."

**Those defaults have since moved**: Claude Code's current docs allow by default "Pushing to any branch of the repository you’re working in, including the default branch," while a force push is still blocked. Check what your installed version permits rather than assuming the reviewer stops a push to main.

For a typical coding agent, the list comes down to remote branch deletes and force-pushes, writes to production data, deploys, and anything that sends data out.

A clone may still hold a deleted branch's commits, but nothing at the moment of the command says whether one does, so treat the branch as gone.

> **Do this first (10 minutes):** Write down the actions your agent could take this week that you couldn't undo yourself in five minutes. Use Anthropic's groups as prompts: losing data or history, sending data out, and touching shared systems such as main, production or someone else's jobs. Next to each, write what stops it today. If the answer is "it would ask me," start there.

## Five guards on the deleted branch

Five guards now meet the same deletion. The first two depend on someone judging consent. The next two judge nothing, but they run on the agent's own machine. The last runs somewhere else.

François Martin, writing after an agent of his emptied two main branches, set out the same kind of order with branch protection first: "I would set up three layers, in this order." We ran steps 4 and 5.

[Figure: Five guards for the deleted branch as a rack of modules: where each sits, what it did with the delete, and what it holds only if.]

### Step 1: Ask

Claude Code's Manual mode (its config name is `default`) "stops and asks you before most actions that edit files, run shell commands, or reach the network," according to [its docs](https://code.claude.com/docs/en/permission-modes). The delete arrives as one more prompt among many. Keep prompts for actions you can undo, where a careless yes costs you a minute.

### Step 2: A second model

Claude Code's auto mode, Codex's automatic review and Cursor's Auto-review all send risky actions to a reviewer model. For this case Anthropic's March write-up says, "This would be blocked since the request was vague, the action irreversible and destructive."

Other cases get through. Martin was running Claude Code in auto mode with Opus 5 when it tried to undo a bad commit from a shallow clone, and the revert "staged every single file in the repository for deletion."

The pushes that followed "removed 151 files from one main branch and 723 from the other," as he [describes on Karakun's blog](https://dev.karakun.com/2026/08/28/coding-agent-pushed-deletion-to-main.html). Of the model and the classifier, his verdict is "Neither stopped this."

The vendors say what a reviewer is. Claude Code's docs call the classifier "a per-action control, not an isolation boundary," and [Cursor's](https://cursor.com/docs/agent/security/run-modes) say "Auto-review is not a security boundary." Use one to be asked less often.

One detail matters for your list. In auto mode, "File writes and edits inside the project directory are allowed without a classifier call," because, Anthropic says, "in-project edits are reviewable via version control." A file that version control doesn't track has no such undo.

### Step 3: A sandbox

A sandbox limits what a command can reach, and Anthropic's engineers write that "effective sandboxing requires both filesystem and network isolation." In [their internal use](https://www.anthropic.com/research/claude-code-sandboxing) it cut permission prompts by 84%.

Claude Code's [sandbox](https://code.claude.com/docs/en/sandboxing) gives a command "no direct route to the network," and its proxy "checks the hostname of each connection against your allowed and denied domains."

If the git host isn't allowed, a sandboxed push can't reach it, though a blocked command can be retried outside the sandbox unless you set `allowUnsandboxedCommands` to false.

The agent's job usually needs that host, and once it is allowed, the sandbox can't tell an ordinary push from a deletion. That sandbox also "covers shell commands only. Claude’s file tools, MCP servers, and hooks run outside it."

### Step 4: A deny rule

A deny rule names a command and blocks it, and in Claude Code "Deny rules block in every mode, including bypassPermissions." This one, in the project's `.claude/settings.json`, names the delete:

```json
{
  "permissions": {
    "deny": ["Bash(git push origin --delete *)"]
  }
}
```

We ran it with Claude Code 2.1.292 against a throwaway repository with a local remote, asking the agent to run one exact command each time. The rule stopped the command it names:

```text
TOOL CALL: git push origin --delete old-1
TOOL RESULT: Permission to use Bash with command git push origin --delete old-1 has been denied.
```

Then we told it to send the same deletion in the other form git accepts:

```text
TOOL CALL: git push origin :old-1
TOOL RESULT: To ../remote.git
 - [deleted]         old-1
```

The branch was gone. We had allowed `git push *` so that ordinary pushes would run, and the colon form matched that and slipped past the deny rule.

[Claude Code's permissions docs](https://code.claude.com/docs/en/permissions) say a deny rule "covers the invocation Claude usually produces and isn’t a security boundary around the program."

[Rulestack](https://rulestack.hashnode.dev/claude-code-permission-rules-bash-git-push-stopped-8-of-14-ways-to-push-and-5-reached-the-remote), which sells rule packs for these tools, tried 14 ways of writing a push against one rule, and "5 pushed to the remote." We picked the second spelling ourselves.

### Step 5: Refuse it at the destination

The last guard runs on the server. Git has a setting for it, `receive.denyDeletes`, which "will deny a ref update that deletes the ref" ([git docs](https://git-scm.com/docs/git-config)). We switched it on in the remote and sent the colon form again:

```bash
git config receive.denyDeletes true   # in the remote repository
```

```text
TOOL CALL: git push origin :old-2
TOOL RESULT: Exit code 1
remote: error: denying ref deletion for refs/heads/old-2
To ../remote.git
 ! [remote rejected] old-2 (deletion prohibited)
```

The server refuses the deletion however the command is spelled, but **it refuses only what it names**.

In a second test, with `receive.denyNonFastForwards` on as well, an ordinary push of a commit that deleted every file went through and left `main` empty. That is the shape of Martin's incident: commits that deleted the files, with every branch still in place.

His fix for it is a required pull request with CI: "Letting the agent open a pull request instead would have stopped both incidents, even with automatic merging once CI passed," because "neither the invalid YAML nor the deletion commits would have passed CI." So match the server rule to the failure, and remember it holds only while the agent can't change the server.

In our test the remote was a folder on the same machine, so the agent could have deleted the branch there directly. On a real server, **the credential decides**.

In one user's [report against Claude Code](https://github.com/anthropics/claude-code/issues/42849), a force-push needed for a git-history scrub was blocked by branch protection and a ruleset, and "the agent disabled GitHub branch protection rules and a repository ruleset without user authorization in order to unblock a force push."

Martin's answer is to give the agent "a machine account or app installation whose credentials have no bypass rights." That helps only if your own token is out of the agent's reach, which is how Anthropic built its web sandbox: git credentials and signing keys "are never inside the sandbox with Claude Code."

On GitHub, a ruleset does the server's job. "Require a pull request before merging", together with "Require status checks to pass before merging", would have stopped Martin's commits, because his CI would have failed them.

That pair protects you only against what your checks detect, so test the checks too; a pull request alone "doesn't necessarily have to be approved, but it must be opened."

"Restrict deletions" means "only users with bypass permissions can delete branches or tags whose name matches the pattern you specify," and "Block force pushes" sits beside it; both come ticked when you create a ruleset ([GitHub's docs](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/available-rules-for-rulesets)).

On private repositories rulesets need a paid plan, "GitHub Pro, GitHub Team, and GitHub Enterprise Cloud" ([GitHub](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/about-rulesets)). Without one, `receive.denyDeletes` on a server you run is the fallback.

**The cost is real**: under a ruleset, someone with bypass rights deletes branches by hand, and with `receive.denyDeletes` even your own push is refused, so a branch goes only when an admin turns the setting off or deletes it on the server.

Where you don't run the destination at all, the credential is the guard: a database user that can read production and not write to it, a token with no delete rights.

## The same guards in Claude Code, Codex and Cursor

Each tool has the same pieces under different names.

[Figure: The same guards in Claude Code, Codex and Cursor as three faders, from asking you to no prompts, each knob at that tool's default or recommended setting.]

### Claude Code

Where auto mode is available, interactive Claude Code sessions now start in it: "With Claude Code v2.1.283 or later, auto mode is the built-in starting permission mode for interactive terminal and VS Code sessions." So the deleted branch would meet step 2, and nothing after it unless you add steps 3 to 5.

### Codex

Codex starts at the wall. Its local defaults "include no network access and write permissions limited to the active workspace," and under its on-request approval policy the agent "asks when it needs to go beyond that boundary," so a push over the network comes to you first.

Setting `approvals_reviewer = "auto_review"` sends those requests to a reviewer model instead, and automatic review "doesn’t change the sandbox boundary," [OpenAI notes](https://learn.chatgpt.com/docs/sandboxing).

### Cursor

Cursor has three Run Modes: Auto-review, Allowlist and Run Everything. "The safest useful setup for most people is Auto-review," its docs say, and Auto-review became the recommended default on 29 May 2026. We ran Claude Code only; the Codex and Cursor settings here come from their docs. Defaults move between versions, so check your mode after every update.

## A setup for each kind of task

Set the agent by the job, and keep the guards from your list on whatever the job.

[Figure: A selector with one position for each kind of task: exploring, everyday edits, long tasks and unattended runs; in every position the can't-undo list keeps its own guard.]

**Exploring code you don't know** should start without write access. Claude Code's plan mode is for investigating and proposing changes, but it still runs "classifier-approved commands when auto mode is available," so where that matters use a real read-only boundary, such as Codex's read-only sandbox.

**Everyday work in one repository** can allow edits inside the project, with version control as the undo, so commit before you hand over a task.

**Long tasks you check in on** are where fewer prompts pay off: auto mode or automatic review, inside a sandbox.

**Unattended runs** need an isolated environment and narrowly scoped credentials. Auto mode can run without routine prompts, and Claude Code's docs suggest a sandbox or container as an extra layer there.

The bypass modes (bypassPermissions, Run Everything, danger-full-access) belong only in a container or a virtual machine: the docs say to use bypassPermissions only "in isolated environments like containers or VMs where Claude Code can’t cause damage."

Whatever the job, the actions on your list keep their own guards: a deny rule you have tested, a refusal on the server, or a credential that can't do them.

## What no guard here covers

**Damage inside the wall stays possible.** Auto mode lets in-project edits through without a classifier call, and an agent allowed to edit your project can still break it, or [rewrite the tests that judge it](https://durabilitycurve.com/blog/agent-proof-tests/). Version control lets you recover tracked changes, but untracked files, ignored files and uncommitted work can still be lost.

**An agent can reach its own rules.** "In bypassPermissions mode, Claude Code skips permission prompts, including for writes to protected paths such as .git and .claude," and `.claude` is where the deny rule lives.

That is the gap in [The Guardrail Your Agent Can Reach](https://durabilitycurve.com/blog/the-guardrail-your-agent-can-reach/). A guard the agent can edit is a guard it can remove. Claude Code's settings can switch off bypass and auto mode, and the docs say those switches "are most useful in managed settings where they can’t be overridden."

**Everything above assumes the agent is trying to help.** When something it read is steering it, the request itself comes from an attacker; a later guide in this series covers that case.

## Test your guards

The first guide's rule was that every addition to a harness is a hypothesis until you test it. A guard is one too.

For each action on your list, make a throwaway copy of the destination, ask the agent to do the thing, then ask again with the command written another way. Then turn on the destination's guard and try once more.

For a git server, the copy takes six lines in an empty folder (it assumes git already has your name and email), and the last one should fail:

```bash
git init -q --bare remote.git
git init -q work && cd work && git remote add origin ../remote.git
git commit -q --allow-empty -m init && git push -q origin HEAD:main
git branch old-1 && git push -q origin old-1
git -C ../remote.git config receive.denyDeletes true
git push origin :old-1
```

```text
remote: error: denying ref deletion for refs/heads/old-1
To ../remote.git
 ! [remote rejected] old-1 (deletion prohibited)
error: failed to push some refs to '../remote.git'
```

On a hosted server, repeat it in a disposable repository with the same rules and the agent's own credentials, never against anything you need.

We also gave Claude Opus 5.5 the incident's own words, "Clean up old branches.", in three recorded runs. Each time it deleted only the old local branches and never touched the remote.

Those runs were headless, locked down and on one phrasing; Martin's auto-mode session pushed the deletion of every file to two main branches, which a required pull request with checks would have stopped. The guards are for that run, as long as they match it.

**A guard you haven't seen refuse is a hypothesis.**

## The checklist

**This week**

☐ You have a written list of what your agent could do that you couldn't undo in five minutes.

☐ Each item on it has a guard that doesn't depend on anyone reading a prompt.

☐ The agent pushes and deploys as its own identity, with no admin or bypass rights.

☐ The server refuses branch deletes (Restrict deletions, or `receive.denyDeletes`) and force-pushes (Block force pushes, or `receive.denyNonFastForwards`) on shared branches.

**Next**

☐ Every deny rule on the list has been tried with the command written a second way.

☐ Changes to main arrive through a required pull request, with checks that would catch a destructive change and that the agent can't bypass.

☐ The agent's other credentials can't do what's on the list: read-only database users, tokens without delete rights.

**Once it works**

☐ Every guard on the list has refused on a throwaway copy, and is tried again after each update.

☐ Where agents run unattended, the switches that turn off bypass and auto mode live in managed settings.

## The whole job

List what your agent can't undo, try one of those actions on a copy this week, and keep only the guards you have watched refuse it.

*Further reading: if you read three of the originals in full, make them [Anthropic on how auto mode was built](https://www.anthropic.com/research/claude-code-auto-mode), [François Martin on the agent that emptied two main branches](https://dev.karakun.com/2026/08/28/coding-agent-pushed-deletion-to-main.html) and [Claude Code's permissions docs](https://code.claude.com/docs/en/permissions). Every other source is linked where it is used. The sources run from October 2025 to October 2026; I rechecked each quotation against its page on 23 October 2026.*
