<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Manish Kapoor</title>
    <description>The latest articles on DEV Community by Manish Kapoor (@kapoormanish).</description>
    <link>https://dev.to/kapoormanish</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3991757%2F874808b4-3ac2-4259-af00-c6170a868ee6.png</url>
      <title>DEV Community: Manish Kapoor</title>
      <link>https://dev.to/kapoormanish</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kapoormanish"/>
    <language>en</language>
    <item>
      <title>Stop Clicking Approve: How to Customize Claude Code CLI Permissions</title>
      <dc:creator>Manish Kapoor</dc:creator>
      <pubDate>Mon, 22 Jun 2026 12:32:38 +0000</pubDate>
      <link>https://dev.to/kapoormanish/stop-clicking-approve-how-to-customize-claude-code-cli-permissions-pnh</link>
      <guid>https://dev.to/kapoormanish/stop-clicking-approve-how-to-customize-claude-code-cli-permissions-pnh</guid>
      <description>&lt;p&gt;The first time I ran Claude Code on a real task — migrate a Spring Boot service, restructure a few packages, update some config — I spent more time clicking &lt;strong&gt;Approve&lt;/strong&gt; than I did reviewing what it actually did.&lt;/p&gt;

&lt;p&gt;Every file read. Every &lt;code&gt;git status&lt;/code&gt;. Every &lt;code&gt;ls&lt;/code&gt;. Approve. Approve. Approve.&lt;/p&gt;

&lt;p&gt;By the time Claude finished, I'd clicked through forty-something prompts and was no closer to trusting what had changed than when I started.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The permission system was doing the opposite of what it should: instead of giving me control, it had trained me to rubber-stamp everything without reading it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's the problem this post is about. Not how to skip permissions entirely — that's a different conversation — but how to configure them so you're approving things that actually matter, and not thinking twice about the ones that don't.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why the permission system exists
&lt;/h2&gt;

&lt;p&gt;Claude Code is not a chat window. It can read files, write files, run arbitrary shell commands, call external services through MCP, and chain all of that into multi-step work without stopping between steps. That's what makes it useful for real tasks. It's also why a blanket "just approve everything" is a bad idea on any machine you care about.&lt;/p&gt;

&lt;p&gt;The permission system sits between Claude's decisions and your filesystem. Every time Claude wants to do something that could modify state — write a file, run a bash command, push to a remote — the harness checks whether it's allowed to proceed automatically, needs to ask you, or should be blocked outright.&lt;/p&gt;

&lt;p&gt;Get the configuration right and you spend your attention on the decisions that actually need it. Get it wrong and you either click yourself numb or give Claude the keys to everything.&lt;/p&gt;




&lt;h2&gt;
  
  
  The four permission modes
&lt;/h2&gt;

&lt;p&gt;Before rules, there are modes. The mode is the default behaviour for anything that doesn't match a specific rule.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;Reads&lt;/th&gt;
&lt;th&gt;Edits&lt;/th&gt;
&lt;th&gt;Bash&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;default&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Auto&lt;/td&gt;
&lt;td&gt;Asks&lt;/td&gt;
&lt;td&gt;Asks&lt;/td&gt;
&lt;td&gt;Safe starting point, new projects&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;plan&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Auto&lt;/td&gt;
&lt;td&gt;Blocked&lt;/td&gt;
&lt;td&gt;Blocked&lt;/td&gt;
&lt;td&gt;Exploring or reviewing a codebase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;acceptEdits&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Auto&lt;/td&gt;
&lt;td&gt;Auto&lt;/td&gt;
&lt;td&gt;Asks&lt;/td&gt;
&lt;td&gt;Active development sessions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;bypassPermissions&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Auto&lt;/td&gt;
&lt;td&gt;Auto&lt;/td&gt;
&lt;td&gt;Auto&lt;/td&gt;
&lt;td&gt;CI/CD pipelines and isolated containers&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You switch modes mid-session with the &lt;code&gt;/permissions&lt;/code&gt; command. You set the default in &lt;code&gt;settings.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"permissions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"defaultMode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"acceptEdits"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Valid values are &lt;code&gt;default&lt;/code&gt;, &lt;code&gt;plan&lt;/code&gt;, &lt;code&gt;acceptEdits&lt;/code&gt;, &lt;code&gt;dontAsk&lt;/code&gt;, and &lt;code&gt;bypassPermissions&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where settings live
&lt;/h2&gt;

&lt;p&gt;Claude Code reads configuration from multiple locations, in order of priority from highest to lowest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Enterprise managed policy   → ~/.claude/managed-settings.json
User settings               → ~/.claude/settings.json
Project settings            → .claude/settings.json
Project local (gitignored)  → .claude/settings.local.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Higher scope wins. If a managed policy denies a permission, nothing in your user or project settings can override it. If your project settings deny &lt;code&gt;Bash(curl *)&lt;/code&gt;, your user settings cannot allow it back.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Rule of thumb:&lt;/strong&gt; commit &lt;code&gt;settings.json&lt;/code&gt; so the whole team gets the same base config. Put personal overrides in &lt;code&gt;settings.local.json&lt;/code&gt; and keep it gitignored.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Allow and deny rules
&lt;/h2&gt;

&lt;p&gt;Rules live inside the &lt;code&gt;permissions&lt;/code&gt; object and follow a consistent format: either a bare tool name, or a tool name with a specifier in parentheses.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"permissions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"allow"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Bash(npm run *)"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"deny"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Bash(rm *)"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ask"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Bash(git push *)"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Evaluation order: deny → ask → allow. The first match wins, and deny always wins.&lt;/strong&gt; A narrow allow rule cannot override a broader deny rule. If you write &lt;code&gt;deny: ["Bash(aws *)"]&lt;/code&gt; and then &lt;code&gt;allow: ["Bash(aws s3 ls)"]&lt;/code&gt;, the allow rule never fires.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Bare tool name vs scoped rule behave differently.&lt;/strong&gt; This is the part that catches people out.&lt;/p&gt;

&lt;p&gt;A bare tool name like &lt;code&gt;"Bash"&lt;/code&gt; in the deny list removes the tool from Claude's context entirely. Claude never sees it exists. A scoped rule like &lt;code&gt;"Bash(rm *)"&lt;/code&gt; leaves the tool available but blocks matching calls when Claude attempts them.&lt;/p&gt;

&lt;p&gt;Use bare names when you want to disable a tool completely. Use scoped rules when you want the tool available but constrained.&lt;/p&gt;




&lt;h2&gt;
  
  
  The tools you can target
&lt;/h2&gt;

&lt;p&gt;The main tools you'll write rules for:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;What it covers&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Bash&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Shell command execution — by far the most important to configure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Read&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Reading files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Edit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Editing existing files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Write&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Creating new files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Grep&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Searching file contents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Glob&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Listing files by pattern&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;WebFetch&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Fetching URLs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mcp__&amp;lt;server&amp;gt;__&amp;lt;tool&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Any MCP server tool&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For Bash rules, the specifier is a glob matched against the full command string: &lt;code&gt;Bash(git *)&lt;/code&gt; matches any git command, &lt;code&gt;Bash(npm run *)&lt;/code&gt; matches any npm script, &lt;code&gt;Bash(git push *)&lt;/code&gt; matches git pushes specifically.&lt;/p&gt;




&lt;h2&gt;
  
  
  Practical configurations for real scenarios
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Daily development on a project you own
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Use this when you're in an active coding session and have already agreed on the plan with Claude. You want edits to flow freely but still get a pause before pushes and destructive commands.&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"permissions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"defaultMode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"acceptEdits"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"allow"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Bash(git status)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Bash(git log *)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Bash(git diff *)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Bash(npm run *)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Bash(npm test)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Bash(mvn test)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Bash(./gradlew test)"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"deny"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Bash(rm -rf *)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Bash(curl *)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Bash(wget *)"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ask"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Bash(git push *)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Bash(git commit *)"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Exploring or reviewing an unfamiliar codebase
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Use this when you want Claude to analyse and explain but not touch anything — onboarding to a new repo, doing an architecture review, or talking through a design before agreeing on what to change.&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"permissions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"defaultMode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"plan"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"allow"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Read"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Grep"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Glob"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  CI/CD pipeline (GitHub Actions)
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Use this for unattended runs in an isolated environment. Even in bypass mode, deny rules still fire — use them to block anything that could escape the container.&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"permissions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"defaultMode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bypassPermissions"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"deny"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Bash(git push *)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Bash(curl *)"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Protecting sensitive files regardless of mode
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Use this to prevent Claude from ever reading credentials or environment files, no matter what mode you're in.&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"permissions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"deny"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Read(./.env)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Read(./.env*)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Bash(cat .env*)"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The /permissions command
&lt;/h2&gt;

&lt;p&gt;Instead of editing JSON by hand every session, use the built-in &lt;code&gt;/permissions&lt;/code&gt; command inside Claude Code. It opens an interactive UI where you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;See your current mode&lt;/li&gt;
&lt;li&gt;Switch modes for the current session&lt;/li&gt;
&lt;li&gt;Add allow or deny rules that persist to &lt;code&gt;settings.local.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Review what's been auto-approved so far&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For anything you find yourself approving repeatedly in a session, it's faster to add it through &lt;code&gt;/permissions&lt;/code&gt; than to keep clicking.&lt;/p&gt;




&lt;h2&gt;
  
  
  CLI flags for one-off sessions
&lt;/h2&gt;

&lt;p&gt;If you don't want to touch &lt;code&gt;settings.json&lt;/code&gt;, you can set rules for a single session at startup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Allow specific tools for this session only&lt;/span&gt;
claude &lt;span class="nt"&gt;--allowedTools&lt;/span&gt; &lt;span class="s2"&gt;"Read,Grep,Glob,Bash(git log*),Bash(git diff*)"&lt;/span&gt;

&lt;span class="c"&gt;# Block specific tools for this session only&lt;/span&gt;
claude &lt;span class="nt"&gt;--disallowedTools&lt;/span&gt; &lt;span class="s2"&gt;"Bash(rm *),Bash(curl *)"&lt;/span&gt;

&lt;span class="c"&gt;# Set the permission mode for this session&lt;/span&gt;
claude &lt;span class="nt"&gt;--permission-mode&lt;/span&gt; acceptEdits

&lt;span class="c"&gt;# Headless one-shot task with no prompts (CI use case)&lt;/span&gt;
claude &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"Run all tests and report failures"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--permission-mode&lt;/span&gt; bypassPermissions &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--disallowedTools&lt;/span&gt; &lt;span class="s2"&gt;"Bash(git push *)"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Flags apply only to the current session and don't modify any settings file.&lt;/p&gt;




&lt;h2&gt;
  
  
  The one thing most people get wrong
&lt;/h2&gt;

&lt;p&gt;:::warning&lt;br&gt;
&lt;code&gt;bypassPermissions&lt;/code&gt; does not make your deny rules irrelevant. Deny rules are evaluated before the permission mode — they always fire, even in bypass.&lt;br&gt;
:::&lt;/p&gt;

&lt;p&gt;This is the mechanism that makes bypass safe in CI: you can allow everything except the specific things that could escape the environment.&lt;/p&gt;

&lt;p&gt;What &lt;code&gt;bypassPermissions&lt;/code&gt; does is skip the "ask" step for everything that isn't explicitly denied. So the right pattern for a CI pipeline is not "bypass with no deny rules" — that's YOLO mode — but "bypass with deny rules for anything that could cause damage outside the container."&lt;/p&gt;




&lt;h2&gt;
  
  
  Where to go next
&lt;/h2&gt;

&lt;p&gt;The official documentation covers hooks (&lt;code&gt;PreToolUse&lt;/code&gt;, &lt;code&gt;PostToolUse&lt;/code&gt;) which let you run custom shell commands at each tool invocation — useful for enforcing things the rule syntax can't express, like blocking edits to files that match a pattern across multiple tools at once.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;/permissions&lt;/code&gt; command and &lt;code&gt;settings.json&lt;/code&gt; are enough for most workflows. Start there, pay attention to what you're clicking approve on in your first few sessions, and add rules for anything you approve more than twice. Within a week you'll have a configuration that fits how you actually work — and you'll stop rubber-stamping things you haven't read.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is part of my series on practical AI tooling for software architects. If something here was useful, or if you've found a permissions pattern that works well for your workflow, I'd like to hear about it in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>ai</category>
      <category>devtools</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
