Tips & Tricks (Updated: 6/7/2026)

Where to Draw the Line: Claude Code Approvals and Sandbox in Daily Work

Split allow/ask/deny, pick the right permission mode, avoid dangerously-skip-permissions, and learn when a sandbox actually helps.

Where to Draw the Line: Claude Code Approvals and Sandbox in Daily Work

“The approval dialog pops up anyway, so I’m probably fine.”

That’s what I told myself when I first started using Claude Code. Every time it tried to do something, a confirmation appeared. I’d read it, hit OK, and feel like a careful driver.

Two weeks later I noticed how my own fingers were moving, and it stopped me cold. I was hitting Enter on reflex, without reading the confirmation text. Once an action became “the usual flow,” I wasn’t actually checking it anymore. git push, writes to external APIs — I was waving them all through without looking. Nothing blew up, but that was luck, not skill.

More approval dialogs do not make you safer. Too many and people stop reading. Too few and irreversible actions slip through. Today I want to write about where to draw that line — not the config syntax, but how you actually decide, day to day.

Key takeaways

  • Approval isn’t “block everything” or “allow everything.” The axis is fast for reversible work, slow for irreversible work. Draw the line by asking whether you can undo it.
  • Switch permission modes (default / acceptEdits / plan / auto / dontAsk / bypassPermissions) by work phase. Use plan to explore, acceptEdits when you’re reviewing as you go.
  • Don’t reach for --dangerously-skip-permissions (= bypassPermissions) in daily work. Use auto mode or a sandbox instead.
  • A sandbox narrows where Bash can reach, at the OS level. It runs on macOS / Linux / WSL2 and does not run on native Windows.
  • On a team, don’t pin “where you stop” to anyone’s discipline. Lock it in physically with deny rules and hooks.

The settings file syntax itself lives in the companion piece, the Claude Code permissions guide, and concrete recipes for combining CLAUDE.md with permissions are in the CLAUDE.md and permission recipes. This article sticks to the operational call: where to stop.

Draw the line by asking “can I undo it?”

When you’re unsure where the line goes, ask yourself one question before you weigh the technical risk:

“Can I take this back five minutes from now?”

Reading a file, grepping, viewing a diff, running npm run build — you can redo these as many times as you like. Worst case, git checkout puts it back. So you want them fast. By contrast, git push, a production deploy, sending email, writing to an external database — the moment you press the button, the outside world changes. You can’t take it back. So you slow these down on purpose.

Here’s the three-tier split I lean on:

ActionCallReason
Read / grep / diff / build / test / lintallowReversible. Don’t slow it down
Editing code on a branchask or acceptEditsDepends on repo maturity
push / deploy / publish / send email / external API writesaskTouches the outside world. Irreversible
Reading .env / rm -rf / git reset --hard / curl | shdenyThe blast radius is too big when it goes wrong

The row people agonize over is “editing.” Editing isn’t dangerous across the board. In a personal repo with solid tests, running edits fast isn’t scary at all. What’s actually scary is editing unchecked in a production-leaning repo with weak tests. So the criterion isn’t “do I allow edits?” — it’s what do I verify with afterward? If your verification commands are in place, edits can be fast. If they aren’t, leave editing in ask.

It never hurts to set deny a little wide, just in case. Reading .env and destructive commands can be blocked from day one, on the assumption you’ll never need to promote them to allow. The “skip permissions and go as fast as possible” style of instruction I called out in dangerous prompt patterns is an act of deliberately breaking this boundary. Put those in deny and the engine stops them no matter what a prompt says.

Switch permission modes by phase

If allow/ask/deny decides what gets stopped, permission mode is the gear you pick for how much gets stopped right now. Shift+Tab cycles through default → acceptEdits → plan. The official permission modes page lists them all, but here’s the mapping that matters in practice:

ModeRuns without askingWhen to use
defaultRead-onlyA repo you’re new to, work you want to take slowly
planRead-only (plans without editing)When you want to understand the codebase before acting
acceptEditsReads + edits inside the working folderFixing things while reviewing them yourself
autoAlmost everything (a classifier checks safety behind the scenes)Long sessions, when you want less approval fatigue
dontAskOnly what you pre-allowedCI or scripts in a locked-down environment
bypassPermissionsEverything (no checks)Inside an isolated container or VM only

My routine is simple. The entrance to any new job is plan. I let Claude read first and lay out the steps, confirm the direction is right, and only then let it move. Once the direction is locked, I drop to acceptEdits and let it edit in one pass, then read the whole thing at once with git diff. Reviewing it all afterward beats approving line by line — I read with more focus. This is about making “always look at the end” a system, and it’s the same continuous thread as the “put the gatekeeper on the machine” idea I wrote about in harness engineering.

One caution. In every mode except bypassPermissions, writes to protected paths are never auto-approved. Those are places like .git, .claude, .bashrc, and .npmrc — break them and the repo or your config itself dies. Even when you’re happily editing in acceptEdits, a confirmation still appears here. Don’t think of it as friction; think of it as the last safety net.

Getting by without --dangerously-skip-permissions

Trying to solve “too many confirmations is annoying,” a lot of people reach for --dangerously-skip-permissions (= bypassPermissions mode). As the name says, it’s a flag that turns off every check.

If you make that your daily driver in interactive work, you no longer have a supervised agent. You just haven’t had the accident yet. The official docs explicitly scope this mode to “inside an isolated container or VM,” and aside from worst cases like rm -rf / or rm -rf ~, you get no prompts at all. Its defense against prompt injection is zero.

But the feeling — “this is annoying” — is legitimate. So what you should kill is the number of confirmations, not the safety net. There are two alternatives.

One is auto mode. It removes confirmations, but a separate classifier model watches each action behind the scenes and blocks the dangerous ones — curl | bash, production deploys, direct pushes to main, force pushes. If you say “don’t push” mid-conversation, that works as a temporary block signal too. Fewer confirmations, but dangerous actions still stop. It’s a different animal from bypassPermissions. That said, auto mode is a research preview, so it’s not a tool for skipping review of sensitive operations wholesale — it’s for work where you already trust the direction.

The other is the sandbox, which the next section covers.

If you want fewer confirmations, reach for acceptEdits or auto first, and a sandbox if that’s still not enough. Treat bypassPermissions as container-only. Think in that order and there’s almost no reason left to strip every safety net off.

A sandbox binds “what it can reach” at the OS level

If approval is the mechanism that asks “may I perform this action?”, a sandbox is the mechanism that narrows the actual range of files and network Bash can reach, at the OS level. Approval is a pre-flight check; the sandbox is a wall during the flight. They sit on different layers.

Where this earns its keep is that it covers approval’s blind spot. Approval judges by looking at the command string, so it can’t notice if npm run build does something unexpected behind the scenes. With a sandbox, that command can write only inside the working folder by default. Even if it doesn’t behave the way its name implies, the OS physically stops it.

Running /sandbox opens a config panel with two modes to choose from:

  • auto-allow: inside the sandbox, Bash runs without confirmation (it’s safe because the range is contained). Only actions that step outside the range fall back to normal approval.
  • regular permissions: even inside the sandbox, you still get the usual approvals. Stronger control, more confirmations.

So it’s a good fit for “I want fewer confirmations, but I’m scared of things running unchecked.” Because the range protects you, you don’t have to be asked every single time.

There’s an important constraint, though. The sandbox runs on macOS / Linux / WSL2 only — it does not run on native Windows. Windows users need to run Claude Code inside WSL2. If you can’t use WSL, or you’re on plain Windows, don’t count on the sandbox — make your allow/ask/deny split stricter instead. Lean deploy, publish, and secret-adjacent commands toward ask in particular. That’s the realistic landing spot on Windows.

Sandbox config looks like this. You punch explicit holes only for tools that need to write outside the working folder (for example, kubectl touching ~/.kube):

{
  "$schema": "https://json.schemastore.org/claude-code-settings.json",
  "sandbox": {
    "enabled": true,
    "failIfUnavailable": false,
    "filesystem": {
      "allowWrite": ["~/.kube", "/tmp/build"],
      "denyRead": ["~/.aws", "~/.ssh"]
    }
  }
}

failIfUnavailable: false means “if the sandbox isn’t available in this environment, just warn and fall back to normal execution.” If your organization wants the sandbox to be mandatory, set it to true and startup itself stops. The quietly important one is denyRead. By default the sandbox can read nearly every file, so it’s reassuring to explicitly seal off secrets like ~/.aws and ~/.ssh.

A starting point you can drop into “daily work”

Boil everything above into one file and this is plenty as a daily starting point. The fine-grained meaning of the syntax (the difference between Bash(npm run build) and Bash(npm*), for instance) I leave to the permissions guide.

{
  "$schema": "https://json.schemastore.org/claude-code-settings.json",
  "permissions": {
    "defaultMode": "plan",
    "allow": [
      "Read",
      "Grep",
      "Glob",
      "Bash(npm run build)",
      "Bash(npm run test)",
      "Bash(npm run lint)"
    ],
    "ask": [
      "Bash(git push *)",
      "Bash(npx wrangler pages deploy *)",
      "Bash(node scripts/outreach-send-mails.mjs --send)",
      "WebFetch(domain:api.gumroad.com)"
    ],
    "deny": [
      "Read(./.env)",
      "Read(./.env.*)",
      "Bash(rm -rf *)",
      "Bash(git reset --hard *)",
      "Bash(curl * | sh)"
    ]
  },
  "sandbox": {
    "enabled": true,
    "failIfUnavailable": false
  }
}

It’s doing only three things. Keep reads and verification fast. Always stop actions that affect the outside world. Forbid the obviously dangerous from the start. I include defaultMode: "plan" because I want every new session to begin from “read first, then plan.” From there you ratchet up to acceptEdits with Shift+Tab as you work.

Leaving edits (Edit / Write) out of both allow and ask is deliberate — I want to control them on the mode side (acceptEdits). Pin edits with both rules and modes and you can’t tell which one is in effect. Split the roles — edits by mode, external side effects by rule — and your head stays clear.

On a team, pin “where you stop” with something physical

Everything so far has been about the individual. On a team, one more problem appears: the line ends up drawn differently by different people. Some are cautious; some want to run everything in bypassPermissions. Lean on goodwill and good sense and the loosest person’s habits become the team’s effective line.

So don’t leave the places you want to stop to human judgment — pin them with something physical. Three concrete examples:

1. Stop secret leaks before commit. A PreToolUse hook mechanically checks, before git add, whether .env got staged. The commit stops before a person even thinks “oh, that shouldn’t have gone in.”

2. Force a build before deploy. Don’t trust “I’m pretty sure the build passed locally.” Have a hook always run npm run build before the deploy command.

3. Run verification automatically after edits. Run npm run test after Edit / Write. Even a tiny fix triggers verification, so you can’t carry on with something broken.

A minimal setup is about this size. More hooks isn’t better — roughly one blocking hook and one verification hook is what keeps the setup from collapsing under its own weight.

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash(git add*)",
        "hooks": [{
          "type": "command",
          "command": "git diff --cached --name-only | grep -E '^\\.env' && echo 'Blocked: .env staged' && exit 1 || exit 0"
        }]
      },
      {
        "matcher": "Bash(npx wrangler pages deploy*)",
        "hooks": [{
          "type": "command",
          "command": "npm run build"
        }]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [{
          "type": "command",
          "command": "npm run test || true"
        }]
      }
    ]
  }
}

On Windows, swap the grep part for findstr or a small Node script and it works. What matters is the shape, not the exact command: “stop before commit / build before deploy / verify after edits.” To bind things at the org level, you can also disable bypassPermissions in managed settings and make the sandbox mandatory. Once you’re at team scale, designing the operating rules together on the consultation page ends up being the fastest path anyway.

Common failure cases

All three are things I or people around me actually did.

Putting everything in ask and feeling safe

The first day looks safe. But a week later you start hitting OK without reading the confirmation text. This is more dangerous than a weak deny list, because the illusion of “I checked it” lingers. Before you line up ask entries, firm up deny first.

--dangerously-skip-permissions becomes a verbal tic

Taste it once — “wow, that’s fast” — and you can’t go back. But all you did was remove the supervisor. If confirmations are annoying, use auto mode or the sandbox’s auto-allow. There’s nowhere you actually need to strip off the whole safety net.

Mistaking build success for release success

This shows up constantly in both content ops and development. The local build passed, but the public URL is stale, one language didn’t update, the CTA is broken on mobile. Approval and the sandbox cannot prevent this failure. What you need is a post-publish check. I’ve shipped “I thought I shipped it” plenty of times this way.

FAQ

Q. What’s the difference between auto mode and the sandbox’s auto-allow? A. They protect you differently. auto mode has a classifier model decide “is this action dangerous?” and block it. The sandbox’s auto-allow doesn’t judge the content of an action — it binds the range it can reach at the OS level. The former protects by judgment, the latter by range. You can also combine the two.

Q. Can I use the sandbox on Windows? A. Not on native Windows. Run Claude Code inside WSL2 and you can. If you’re on plain Windows, don’t rely on the sandbox — lean deploy, publish, and secret-adjacent commands toward ask, and keep deny strict.

Q. How do I choose between plan mode and acceptEdits mode? A. plan is “read only, no editing,” for the phase where you understand the codebase and lay out the steps. acceptEdits is “pass edits inside the working folder without asking,” for the phase where the direction is locked and you fix in one pass. I start in plan and drop to acceptEdits once we agree.

Q. What if I really do need to run a denied action just once? A. deny is enforced by the Claude Code engine, so a prompt can’t loosen it (that’s the value of deny). If you genuinely need it, remove it from settings on the spot, run it, and put it back when you’re done. The very effort of “temporarily removing it” acts as a brake on dangerous actions.

Q. Don’t hooks and permissions overlap in their roles? A. They don’t. Permissions control “may this action run?”, while hooks control “what must always run before and after execution?” Deterministic checks — stopping secret leaks, building before deploy — are the hook’s job.

What happened when I actually tried it

I dropped this thinking straight back into this site’s daily automation. The biggest change wasn’t the AI’s output quality itself — it was that “at which point does a human stop?” became crisp.

Before, the operation was vague — “make some progress on one thing” — and a run could end with a small tweak to an existing article. Now the flow is fixed: plan to lay out the steps → acceptEdits to edit → a hook builds before deploy → check every language on mobile after publishing. Since I stopped using bypassPermissions and switched to acceptEdits + sandbox, those “wait, did I just do something external?” jolts disappeared.

Rather than handing everything to a clever AI, put a single breath in front of the irreversible actions. It looks like the long way round, but it’s the one I can run with peace of mind every day. Start by keeping the free cheatsheet next to you and filling in your own deny list, one line at a time.

#claude-code #permissions #approval #sandbox #security #workflow
Free

Free PDF: Claude Code Cheatsheet

Enter your email and download the one-page Claude Code cheatsheet for commands, review habits, and safe workflows.

We handle your data with care and never send spam.

Level up your Claude Code workflow

Start with the free PDF, use Gumroad guides when you need repeatable workflows, and book consultation when rollout or revenue paths need human judgment.

Masa

About the Author

Masa

Engineer focused on practical Claude Code workflows. Runs claudecode-lab.com, a 10-language technical media site.