Claude Code Permission Prompts: Stop Approving Everything


--dangerously-skip-permissions appeared in Claude Code as a flag for headless CI pipelines and became the default answer to every permission fatigue complaint within weeks of release. That tells you something. Not about the flag, but about the failure mode it was invented to paper over. The permission system in Claude Code is one of the most important pieces of its architecture and also one of the most reliably misunderstood. Most people hit y seventeen times during their first session and never stop to ask what they just authorized.


The actual design tradeoff here is not subtle: Claude Code operates as an agentic loop that needs real filesystem access, shell execution, and in many configurations, network reach. That is not an accident. It is a deliberate architectural choice, and the permission prompts exist precisely because the surface area of that access is wider than most developers initially expect. The problem is not the prompts. The problem is that most setups treat them as friction to eliminate rather than signals to read.


There are two legitimate ways to stop mashing y. One is to understand what each permission category is actually gating. The other is to configure trust boundaries at the project level so the right permissions are granted structurally rather than interactively. Almost nobody does the second thing correctly on the first try.


What the Permission System Is Actually Protecting

The Four Claude Code Permission Categories by Risk Level

Four Permission Categories — Risk Profile Comparison

Category What It Gates Risk Level
File Reads Reading files from filesystem LOW
File Writes Creating or modifying files MEDIUM
Browser / Network External requests when tools active MEDIUM–HIGH
Shell Execution Runs unsandboxed with your user permissions, env, and credentials HIGHEST

Approving all four with the same reflex loses the risk distinction entirely

Source: Article: Claude Code Permission Prompts


Claude Code segments its permissions into four rough categories: file reads, file writes, shell command execution, and browser or network actions when those tools are active. The prompts are not uniform. A read prompt and a write prompt carry different risk profiles, and a shell execution prompt belongs to a different class entirely. If you are approving all three with the same reflex, you have already lost the thread of what the agent is doing.


The session-based approval model means that permissions granted during one claude session do not automatically persist to the next one by default. This is where people first encounter the exhaustion. A long multi-file refactor session might surface thirty permission prompts across different directories and tool calls. The natural response is to approve faster. The correct response is to ask why the agent needs access to a path you did not expect.


Shell execution deserves the most attention of any category. When Claude Code executes a shell command, it runs unsandboxed on a standard developer machine, with your user permissions, in your active environment, with access to whatever credentials and configs are on your path. A prompt that says Bash: pip install httpx is asking you to run an arbitrary install in your current Python environment. Inside a venv, that is manageable. Outside one, it is a different conversation entirely.


Shell command prompts get batched in perception even when they are not batched in execution. You see five prompts in quick succession and your brain groups them as one event. They are not. Each one is a discrete action in the world. Treating them as a bundle is how you end up with Claude Code having modified three config files you did not know existed.


# What you approved in the session:
Bash: find . -name "*.env" -type f
Bash: cat .env.local
Bash: pip install python-dotenv --quiet
Write: src/config.py
Write: .env.local

That sequence is not hypothetical. It reflects what actually happens during a common configuration-related task. Read that list back slowly. The third and fifth actions are the ones that should have triggered a pause. The agent touched .env.local twice: once to read it, once to write it. Those are not the same permission and they do not carry the same consequence.


Where .claude/settings.json Trust Configuration Lives

A Real Session's Action Sequence — What Each Step Actually Did

5 Approvals — Decoded

1
Bash find . -name "*.env" -type f
DISCOVERY
2
Bash cat .env.local
READ SECRETS
3
Bash ⚠ pip install python-dotenv --quiet
SHOULD PAUSE
4
Write src/config.py
FILE CHANGE
5
Write ⚠ .env.local
SHOULD PAUSE

.env.local was touched twice — read (step 2) and overwritten (step 5). Different permissions, different consequences.

Source: Article: Claude Code Permission Prompts (example sequence)


Claude Code supports project-level settings via a .claude/settings.json file. This is where you define which tools and permissions the agent can use without interactive prompting for a given project. The structure is not complicated, but the defaults are conservative, which means if you have not written one, you are getting prompted for almost everything.


{
  "permissions": {
    "allow": [
      "Bash(git:*)",
      "Bash(npm run:*)",
      "Read(**)",
      "Write(src/**)"
    ],
    "deny": [
      "Bash(curl:*)",
      "Bash(wget:*)",
      "Write(.env*)"
    ]
  }
}

The glob patterns in the allow and deny lists are the actual mechanism. Bash(git:) allows any git subcommand without prompting. Write(src/*) allows writes inside the src directory. The deny list is evaluated after allow, which means you can grant broad read access and then carve out specific paths that should never be touched without going back to edit the allow list every time the shape of the project changes.


The deny rules for environment files are not set by default. This is a gap that is easy to miss on first configuration. Granting Write(*) without a corresponding deny on .env or .pem or .key is a structural oversight, not threat model paranoia. Credentials that exist in plaintext on a developer machine have already accepted a certain risk level; no reason to expand that further by leaving write access open to every file extension.


The --dangerously-skip-permissions flag bypasses the interactive loop entirely. It exists because there are legitimate headless and CI contexts where no human is present to approve actions. Using it on a local development machine because you find the prompts annoying is a different thing, and it is worth being direct about that distinction rather than treating the flag as a preference setting.


# Intended use: unattended pipeline where the repo is ephemeral
claude --dangerously-skip-permissions -p "run the test suite and output results"

What it means on your laptop:

Key Facts: How Claude Code Permission Fatigue Happens

Permission Fatigue — By the Numbers

17 Typical approvals blindly clicked during a first session
30+ Permission prompts in a long multi-file refactor session
4 Distinct permission categories, each with different risk profiles
2 Legitimate ways to stop approving blindly (understand categories or configure trust boundaries)
Shell execution runs unsandboxed with your user permissions, active environment, and all credentials on your PATH — every prompt is a discrete action, not a bundle.

Source: Article: Claude Code Permission Prompts


Every tool call, every write, every shell command runs with no gate and no review


Python Environment State and the Context Problem


Python environment state is where Claude Code permission decisions get concretely expensive. If you launch a Claude Code session without activating your project venv first, and then approve a Bash: pip install prompt, you have just installed a package into your system Python or your base conda environment. This is not a Claude Code problem. It is a context problem. Claude Code makes it worse than a manual workflow because the install happens faster than you would have typed it, which means the feedback loop that normally teaches you this lesson is compressed or absent.


Agentic tools inherit ambient context. Whatever shell environment was active when you launched the agent is the environment the agent operates in. This seems obvious in retrospect and is almost never explicitly considered before the first mistake. A .python-version file or a project-local venv does not automatically protect you unless your shell is already configured to activate it on directory change, for example via pyenv's auto-activation or a direnv hook.


# Check before you launch anything
which python
python --version
pip --version

If any of these point to /usr/bin or /usr/local/bin without a venv prefix,


you are not in your project environment


A project-level .claude/settings.json that restricts Bash(pip:*) entirely will force any install action back into interactive approval territory, regardless of session state. That is not a limitation, it is a checkpoint. The difference between a task that succeeds cleanly and one that leaves behind state debris is often a single unreviewed install or write that happened three steps before the failure surfaced.


There is also the MCP server dimension. If your Claude Code setup is connected to external MCP servers, those servers can surface additional tools, and those tools get their own permission prompts, or in some configurations do not get prompted at all depending on how the trust level is set in .claude/settings.json under the MCP tool entries. An MCP server that worked fine in local development, where you had set a permissive trust level, will behave the same way if you port that config to a shared or CI environment. The permission settings travel with the project file.


Building a Calibrated Approval Workflow


The version of this workflow that actually reduces friction without eliminating safety is one where you spend about five minutes before starting a new project or a new agent task. Review the permission prompts from a previous session if you have notes, or do a dry pass on what the task will likely require. A refactor task in src/ does not need write access to the project root. A test runner task does not need to touch anything outside of the test directory and build artifacts.


Setting narrow write scopes in the project settings and then expanding them explicitly when a task genuinely requires it inverts how most people use the tool. Most people start permissive and wonder why the agent touched unexpected files. Starting narrow and expanding deliberately means every widening of scope is a conscious decision, not a default state inherited from a blank config.


The prompts themselves provide information that most people stop reading after the first session. The tool name, the argument pattern, and the target path are all in the prompt string. If an approval prompt for a write operation shows a path you do not recognize, that is the moment to stop and read the agent's plan output, not to approve and investigate afterward. By the time you finish investigating, the write has already happened.


The gap between what Claude Code is capable of doing and what a developer has consciously authorized at any given moment is exactly the size of their permission fatigue. That gap does not close by making the agent safer. It closes by making the developer's review loop faster and more deliberate, which is a different goal entirely. Tools that generate thirty prompts in a session are not poorly designed. They are doing a lot. The question is whether you want to be the person who actually knows what they did.