← Claude Code Hub
✦ Tip #151 Jul 31, 2026

--dangerously-skip-permissions in Claude Code: why your script exits green without doing anything

Your CI job calls `claude -p`, exits 0, and the commit isn't there. Nothing warned you, which is why half the internet ends up pasting `--dangerously-skip-permissions` into the pipeline.

Comparison of headless Claude Code: on the left what the run hands back (exit code 0, subtype success, is_error false) in green, on the right what actually happened (permission_denials 2, the file missing) in red, and below the three ways out ranked best to worst: allowedTools, permission-mode dontAsk and dangerously-skip-permissions

TL;DR In headless mode a denied permission does not fail the run. claude -p returns exit code 0, "subtype": "success" and "is_error": false even when Claude never touched a file. The only trace is the permission_denials array in the JSON, which nobody reads. Read it and fail your build yourself. And to let Claude actually work, reach for --allowedTools (state exactly what it may do) or --permission-mode dontAsk (deny fast, no retries) before the --dangerously-skip-permissions that everyone ends up pasting.

Your CI job calls claude -p, finishes green, and the commit isn't there. No error, no warning, nothing. You look twice, you can't make sense of it, and you end up where half the internet ends up: pasting --dangerously-skip-permissions into the pipeline so it "stops causing trouble."

It never caused trouble. It went quiet.

What you see, and what actually happened

I ran this in a clean repo, in Manual mode, with a command that isn't in my allow rules:

claude -p "Run this exact shell command and nothing else: touch ./needsperm.txt" \
  --permission-mode default --output-format json

What the run reports back:

EXIT CODE : 0
subtype   : success
is_error  : False
denials   : 2  ['Bash', 'Bash']
file      : NOT created

Exit 0. success. is_error: false. And the file doesn't exist. Claude tried twice, the permission system denied it twice, and the run finished by calling itself a success.

This never bites you in an interactive session: the permission dialog appears and you answer it. With -p there is nobody to ask, so the call is denied and Claude carries on with whatever is left. From the outside, a run that couldn't do its job and one that did it all look identical.

How to catch it, which comes first

The signal is there, just not where you're looking. With --output-format json, every denial is recorded in permission_denials:

out=$(claude -p "$PROMPT" --output-format json)
denied=$(printf '%s' "$out" | jq '.permission_denials | length')

if [ "$denied" -gt 0 ]; then
  echo "Claude was blocked $denied times:"
  printf '%s' "$out" | jq -r '.permission_denials[] |
    "  \(.tool_name): \(.tool_input.command // .tool_input.file_path // "?")"'
  exit 1
fi

Against the run above, that prints:

Claude was blocked 2 times:
  Bash: touch ./needsperm.txt
  Bash: touch ./needsperm.txt

Drop it into the job and your pipeline stops lying to you. This is the part worth copying even if you change nothing else.

And use printf '%s', not echo. The JSON carries \n inside its strings, and zsh (the default shell on macOS) turns those into real newlines, so echo "$out" | jq hands you a parse error that looks like a Claude Code bug and is yours. It works in bash, which is exactly what makes it so annoying to track down.

The four ways out, best to worst

Option What it does Good for
--allowedTools Pre-approves exactly what may run CI where you know what Claude will do
--permission-mode dontAsk Denies anything that would prompt, without waiting Locked-down pipelines and restricted environments
--permission-mode auto A separate classifier reviews each action Long tasks where you can't enumerate everything
--dangerously-skip-permissions Skips the checks Isolated containers and VMs, and nothing else

1. --allowedTools, your default choice

You tell it what it may do and it does it, no dialogs and no open door to anything else:

claude -p "fix the lint errors" \
  --allowedTools "Read" "Edit" "Bash(npm run lint*)"

Verified against the same probe: with the rule in place, the command runs and the file appears.

2. --permission-mode dontAsk, when you'd rather slam the door

It denies outright everything that would have prompted, and lets through only your allow rules, read-only commands, and whatever a PreToolUse hook approves. The docs pitch it at CI pipelines and restricted environments, and the key phrase is this one: "the session never waits for input."

The practical difference against Manual mode is noise. In my probe, Manual burned 3 turns retrying the denied command; dontAsk cut it off in 2, with no retries.

3. --permission-mode auto, with a ceiling worth knowing about

The auto mode classifier reviews each action instead of asking you. It works well, but in headless mode it has an ending: block 3 times in a row or 20 in total and the session aborts, because there is no one to hand the turn to. That's the error you'll see:

Agent aborted: too many classifier denials in headless mode

4. --dangerously-skip-permissions, which is almost never the answer

It's exactly the same thing as --permission-mode bypassPermissions. And one detail says plenty: the auto mode classifier blocks by default launching an autonomous agent loop with no human approval and no sandbox, and it names this flag as its example. Claude Code stops Claude Code from reaching for it.

What that flag doesn't skip (even though the name suggests otherwise)

There's more nuance here than the name implies. Even in bypass mode, these still stand:

  • Your explicit ask rules, which keep opening a dialog
  • Connector tools your organization set to ask
  • MCP tools marked requiresUserInteraction
  • rm -rf / and rm -rf ~, which still prompt as a circuit breaker, including when buried inside $(...), backticks, or <(...)

Plus two refusals that will bite you if you automate:

--dangerously-skip-permissions cannot be used with root/sudo privileges
for security reasons

On Linux and macOS it refuses to start as root or under sudo, unless it's inside a recognized sandbox. And a background session started with --bg won't start in this mode until you've accepted the responsibility dialog once in an interactive session. A freshly built container has never accepted it.

Reference

Mode in headless What happens when something would need permission
default (Manual) Denied, the run continues, and it finishes as success
acceptEdits Edits and common file commands go through (touch yes, chmod no); everything else is denied just as quietly
dontAsk Denied immediately, no retries and no waiting
auto The classifier decides; aborts after 3 blocks in a row or 20 in total
bypassPermissions Runs, except for the exceptions listed above

If your world is the interactive session rather than the pipeline, the map is a different one: the 6 permission modes on Shift+Tab for daily work, the sandbox to run without dialogs on a leash for disk and network, and wiring Claude up as an autonomous agent if what you want is to leave it working overnight.

Official docs: Permission modes · Non-interactive mode · CLI reference

Requirements: --permission-mode dontAsk and the other values ship in Claude Code v2.1.x; the manual alias for default needs v2.1.200 or later. I ran every probe in this tip on v2.1.220. The classifier ceilings, the rm -rf circuit breaker, and the two startup refusals come from the current docs rather than from my own runs.

Free guide

The 51 essentials, as a PDF.

One page per tip. Five chapters. What I actually use daily in production — no theory, no fluff.

  • I. Getting started 10 tips
  • II. Awareness 3 tips
  • III. Mastery 22 tips
  • IV. Autonomy 10 tips
  • V. Comparison 6 tips
Are you a professional Web developer?

You'll receive the guide by email · You join the Gravitas newsletter · Unsubscribe anytime

of 51
#

Wmedia · 51 Tips
Free guide · 51 tips · 5 chapters

The 51 essentials, as a PDF.

Are you a professional Web developer? · Unsubscribe anytime