# Choosing a permission mode per task

> Run one small task with a coding agent in a mode that asks before everything and in one that accepts edits, count the prompts, and choose the mode by what a mistake would cost.

Page: https://schubergphilis.github.io/ai-training/customizing-agents/permission-modes/
License: CC BY-SA 4.0, https://creativecommons.org/licenses/by-sa/4.0/

In this lesson we give a coding agent the same small task twice. The first
time it asks before every edit and every command. The second time it edits
without asking and only stops for commands. We count the prompts in each
run and read what they were for. Then we decide which mode the task
deserved. The setting is the to-do fixture from *Your first session with a
coding agent*, committed to a throwaway git repository, where a wrong edit
costs one `git checkout`.

The mode names and flags here are Claude Code's, checked against the
vendor's documentation on the date at the end of the lesson
(Choose a permission mode, Claude Code documentation). Other coding agents have the same
levels under other names. The reasoning transfers, and the exact flags may
not.

## The modes are presets

In your first session the agent asked before it edited a file, and you
approved one line at a time. That behavior is a setting. **Permission
modes** are preset levels of how much the agent may do without asking,
from approving every action to running unattended. The mode sets the
baseline for the session, and the allowlists you write in the next lesson
widen or narrow it per command. Which mode a fresh session starts in
depends on your plan and your settings. On some plans the built-in
starting mode is `auto`, so check the status bar before you assume the
agent asks (Choose a permission mode, Claude Code documentation).

Claude Code's modes, with what runs without a prompt in each. The last
column is our paraphrase of the vendor's advice on when to use it
(Choose a permission mode, Claude Code documentation).

| Mode (config value) | Runs without asking                                                                          | When the vendor suggests it                      |
| ------------------- | -------------------------------------------------------------------------------------------- | ------------------------------------------------ |
| `default` (Manual)  | Reads                                                                                        | You want to see each action, or the work is sensitive |
| `acceptEdits`       | Reads, file edits in the working directory, and file commands such as `mkdir`, `mv` and `cp` | Code you read back afterwards                    |
| `plan`              | Reads and exploratory shell commands. Edits are blocked until you approve a plan             | Understanding a codebase before you change it    |
| `auto`              | Everything a second model, the classifier, approves                                          | Long tasks where the prompts wear you down       |
| `dontAsk`           | Reads and pre-approved tools. Anything that would prompt is denied instead                   | CI and scripts with an exact allowlist           |
| `bypassPermissions` | Everything, apart from the few actions listed below                                          | Only inside a container or virtual machine that isolates it |

The `acceptEdits` and `bypassPermissions` rows have fine print that
matters for this lesson. `acceptEdits` approves an edit only when
the path is inside the working directory, and any shell command outside
the built-in read-only set still prompts, so the test run in our task
stops for you in that mode too. `bypassPermissions` skips the permission
prompts, including for writes to protected paths such as `.git` that the
other modes guard. A few actions still prompt in it: a tool matched by an
`ask` rule you wrote, and a tool that needs your answer to work. The
vendor's own page limits the mode to isolated containers and virtual
machines, and the tool refuses to start in it as root or under `sudo`,
"for security reasons", as the vendor puts it
(Choose a permission mode, Claude Code documentation).

You pick the mode for one session with a flag, `claude --permission-mode
acceptEdits`, or cycle through the modes in a running session with
`Shift+Tab`. A `defaultMode` entry in a settings file sets where every
session starts, with one exception: `auto` and `bypassPermissions` in a
project's settings files don't take effect, so a repository can't check in
a setting that starts every clone in a hands-off mode
(Choose a permission mode, Claude Code documentation). This lesson uses the flag. Each run then
states its mode in the command that started it.

Here is the decision as a small program. `NO_PROMPT` holds the table above
for the three kinds of action our task uses, and `TASK` is the task as
the agent most often does it: two reads, two edits, one command. The complete file is
`site/examples/customizing-agents/permission-modes/modes.py` in the
repository.

```python
NO_PROMPT = {
    "default": {"read"},
    "acceptEdits": {"read", "edit"},
    "bypassPermissions": {"read", "edit", "run"},
}

TASK = [
    ("read", "todo.py"),
    ("read", "test_todo.py"),
    ("edit", "todo.py"),
    ("edit", "test_todo.py"),
    ("run", "python3 -m unittest -q"),
]


def prompts(mode, actions):
    return [f"{kind} {target}" for kind, target in actions if kind not in NO_PROMPT[mode]]
```

#### Checkpoint: Count the prompts

What does this print, one line per mode?

```python
for mode in ("default", "acceptEdits", "bypassPermissions"):
    count = len(prompts(mode, TASK))
    noun = "prompt" if count == 1 else "prompts"
    print(f"{mode}: {count} {noun}")
```

The program is a model. The real agent may edit a file twice or run an
extra command, and each of those adds a prompt in the modes that ask for
it. Reads don't change the tally, because no mode prompts for a read
inside the working directory. Your counts in the two runs below can differ from the
model by one or two. The comparison stays the same: every edit and every
command prompts in Manual mode, only the commands prompt in `acceptEdits`,
and nothing prompts in `bypassPermissions`.

## Run one: ask before everything

Make a fresh copy of the fixture directory,
`site/examples/coding-with-agents/first-session/fixture-repo/`, outside the
course repository, and change into it. The copy has no git history of its
own, so give it one before the agent touches anything. This commit is the
point you reset to between the runs.

```sh
git init -q && git add -A && git commit -q -m "fixture"
```

Run the tests next. One of them fails on purpose, because that bug is the
first-session lesson's task, and you want to know that before the agent
tells you.

#### Example: The suite before the agent starts

Run this and compare the last line with the output below.

```sh
python3 -m unittest -q
```

Output:

```text
FAILED (failures=1)
```

Start the agent in Manual mode, `claude --permission-mode default`, and
send this brief.

```text
Add an `undo` command to todo.py that marks item N as not done, the
mirror of `done`. Add one test for it to test_todo.py. Then run
`python3 -m unittest -q`. One test, test_done_marks_the_numbered_item,
already fails and is not your task. Leave it. Do not edit todos.json.
```

Now count. Keep a tally of every prompt, and for each one write down what
it asked to do: which file, or which command. Approve each one after you
have read it. Expect an edit to `todo.py`, an edit to `test_todo.py` and
one command, and don't be surprised by a second edit to the same file.
The agent also reads both files first, and those reads never prompt.

When the agent reports done, read the diff with `git diff` and check the
tally against it. Every changed file should have a prompt in your list.
Then reset the fixture to the commit you made with `git checkout -- .` so
the second run starts from the same place.

## Run two: accept the edits

Start the agent again, this time with `claude --permission-mode
acceptEdits`, and send the same brief. The status bar reads
`accept edits on`. Tally again.

This time the two edits are written without a prompt, and the session
stops once, at the test command. The vendor's guidance for this mode is
to review the changes afterwards in your editor or with `git diff`, rather
than approving each edit inline (Choose a permission mode, Claude Code documentation). Do that
now. Read the diff as carefully as you read the prompts in run one,
because this is the first time you see what changed.

Now compare the two runs on one question: what would a mistake have cost?
In run one, a wrong edit shows up in a prompt before it is written, and
you decline it. In run two, the same wrong edit is written, and you find it
in the diff. The fixture is a committed copy you reset with one command,
so in both runs the repair costs the same `git checkout`. The mode changed
when you saw the edit, and the place decided how much a late look could
cost. Choose the mode by what a mistake costs in the place the agent
works, and remember the *blast radius* habit from the safety course: say
out loud what the agent can reach before you press enter.

#### Checkpoint: The same task in your own repository

The `undo` task went well in the fixture. You want the same change in your
team's repository, where you have half a day of uncommitted work in the
working tree. Which mode do you start with, and what do you do first?

- Start in acceptEdits, as in run two. The task is the same size and it went fine.
- Commit or stash your own changes first, then start in acceptEdits and read the diff afterwards.
- Start with --dangerously-skip-permissions. You are on a feature branch, so main is safe.
- Start in `dontAsk` so the agent cannot do anything you have not allowed.

#### Pitfall: Stepping up on the laptop

A developer gets tired of approving commands during a long refactor and
restarts the agent with `--dangerously-skip-permissions` in the main
checkout on their laptop, with their credentials in the environment and
uncommitted work in the tree. Two hours later the agent runs a cleanup
command to "remove build artifacts" and the pattern matches a directory of
notes that was never committed. The rule: step a mode up only inside a
sandbox or a container, where the worst command costs a copy, and never
further than the task in front of you needs. A git worktree or a branch
shares the machine and its credentials with everything else on it, so it
isn't isolation.

## Which mode for which task

The vendor's course on running the tool unattended makes the same point
from the other side: hands-off runs belong in an environment built to be
thrown away, such as a container, and the check afterwards is the review
of what it produced (Claude Code in action, Claude Academy). The mode is the last
thing you choose, after you know where the agent works and what a wrong
action there would cost.

#### Checkpoint: Which mode for which task?

Place each task in the mode you would start it in.

Buckets:

- default (Manual)
- acceptEdits
- plan
- bypassPermissions

Items:

- Work out how an unfamiliar service handles retries and write up the findings, changing nothing
- Add a command and its test in a fixture copy you committed, so one git command resets it, reviewing the diff afterwards
- Change the deploy script on a machine that holds the production credentials
- Run an overnight batch of dependency upgrades inside a throwaway container with no credentials
- Fix a typo in a README in a checkout with half-finished work you are not ready to commit or stash
- Rename a function across a small project in a clean checkout with everything committed, then read the diff

## Exercise

Do the two runs from this lesson, if you haven't already, in a fresh
copy of the fixture with its own first commit: once in Manual mode and
once in `acceptEdits`, with the same brief. For each run write down the
number of prompts and what each one asked for. Then write one line that
names the mode you would use for this task in this place, and why. The
point is to practice choosing a mode from what a mistake costs, before you
do it in a repository that matters.

A good result: the Manual tally has one entry per edit and per command,
the `acceptEdits` tally has only the command, both diffs contain the same
change, and your one line mentions where the agent worked and what a wrong
edit would have cost there. Reflection: in which of your own repositories
would the answer be different, and what's in them that makes it so?

Stretch: Run the task a third time in plan mode and note where the agent stops: it reads and proposes, and the edits wait until you approve the plan.

## Recap

1. A permission mode is a preset for what the agent may do without asking.
   In Claude Code, `default` prompts for every edit and every command apart from reads,
   `acceptEdits` approves edits inside the working directory and still
   prompts for other commands, `plan` blocks edits, and
   `bypassPermissions` skips the permission prompts apart from your own
   `ask` rules and the tools that need your answer
   (Choose a permission mode, Claude Code documentation).
2. The mode sets the baseline, and allowlists refine it per command. Deny
   rules hold in every mode, and allow rules do nothing in
   `bypassPermissions`.
3. Choose the mode by what a mistake would cost in the place the agent
   works, and say the blast radius out loud first.
4. Step a mode up only inside a sandbox or a container, and only as far as
   the task in front of you needs. A worktree or a branch isn't isolation.
5. In a mode that skips prompts, the diff is your review. Read it as
   carefully as you would have read the prompts.

## References

- Choose a permission mode, Claude Code documentation, https://code.claude.com/docs/en/permission-modes
- Claude Code in action, Claude Academy, https://academy.claude.com/courses/claude-code-in-action
