Last week I was moving between several repositories in one session.
A file I wanted to reference sat outside the working directory, and Claude Code refused to read it, exactly as it should have. So I killed the session, relaunched with --add-dir, and watched everything I had built up in that conversation disappear.
Only afterwards did I learn that this no longer requires a restart. It changed in v2.1.234 on August 17. If you have ever ended a session for the same reason, that one sentence may be the most useful thing here.
You can now open /permissions mid-response
Since v2.1.234, /permissions opens while Claude is still working, and the rules you change apply to the remainder of the current turn. /add-dir works mid-session too.
It reads like a small quality-of-life fix. In daily use it removes a choice I had been making badly for months:
| The old options | What it cost |
|---|---|
| Restart the session with wider flags | Everything in the conversation so far is gone |
| Grant broad permissions up front | You lose track of what you actually approved |
I drifted toward the second one. As an indie developer working alone, the rationalization comes easily: nobody else touches this machine, so why not click through. Several months of clicking Don't ask again later, I could no longer describe my own configuration.
The scope of "Don't ask again" now matches what the dialog shows
A second change landed in v2.1.235 on August 18. The text displayed in the confirmation dialog and the scope that Don't ask again actually covers are now always in agreement. When a rule cannot be displayed in full, the Don't ask again option no longer appears at all.
The point of the fix is to prevent a gap between what you thought you approved and what you really approved. Which also means the approvals you accumulated before this version deserve a look, because your memory of them may not be reliable.
Newer versions being safer does nothing about rules already sitting in your settings file. Auditing what is there, then rebuilding, is the natural order.
Count what you have
Permissions live in JSON, so jq can count them directly.
The script below reports how many deny, ask, and allow rules you have, broken down by tool. It reads .claude/settings.json from the project root by default, and accepts a path as an argument.
#!/bin/bash
# Inventory permission rules: counts per category, broken down by tool
F="${1:-.claude/settings.json}"
for KIND in deny ask allow; do
N=$(jq -r ".permissions.$KIND // [] | length" "$F")
echo "== $KIND: $N rules"
jq -r ".permissions.$KIND // [] | .[]" "$F" \
| sed 's/(.*//' | sort | uniq -c | sort -rn \
| awk '{printf " %-10s %s\n", $2, $1}'
done
echo "== additionalDirectories: $(jq -r '.permissions.additionalDirectories // [] | length' "$F")"The sed 's/(.*//' step strips everything from the opening parenthesis onward, turning Bash(git status:*) into plain Bash. The question worth answering is not which individual rules exist but which tool is carrying most of your trust.
Run it against a configuration like this:
{
"permissions": {
"deny": [
"Read(./.env)",
"Read(./**/*.pem)",
"Bash(rm -rf:*)",
"Bash(git push --force:*)"
],
"ask": [
"Bash(git push:*)",
"Bash(npm publish:*)",
"Edit(./content/**)"
],
"allow": [
"Bash(git status:*)",
"Bash(git diff:*)",
"Bash(npm run test:*)",
"Bash(npm run build:*)",
"Read(./src/**)",
"Read(./docs/**)",
"WebFetch(domain:code.claude.com)"
],
"additionalDirectories": ["../shared-assets"]
}
}and you get:
== deny: 4 rules
Read 2
Bash 2
== ask: 3 rules
Bash 2
Edit 1
== allow: 7 rules
Bash 4
Read 2
WebFetch 1
== additionalDirectories: 1
Fourteen rules total is still small enough to read line by line. Once the count climbs past forty or fifty, scanning the list stops telling you anything, and the rule set starts costing you on every turn. If your inventory came back larger than you expected, the walkthrough on sessions that get heavier as permission rules pile up covers how to shrink the set without giving up safety.
One caveat: this only looks at project settings. Your user-level configuration has the same structure, so count both. Which file receives a rule created by Don't ask again depends on the option you pick in the dialog.
Start from the smallest set and add on demand
Once you know what you have, rebuild it. I work through deny, then ask, then allow, because that mirrors how the rules are evaluated.
| Category | Meaning | What belongs here |
|---|---|---|
| deny | Always refused, and it wins over the others | Anything you cannot afford to have touched |
| ask | Confirmed every time | Actions you cannot take back |
| allow | Runs without a prompt | Things you repeat many times a day |
Four concrete steps:
- Fill in
denyfirst. Credential files, private keys,rm -rf,git push --force. When in doubt, add it here — a deny rule costs you a rewrite, a missing one costs you a repository - Put "reversible but visible outside" actions in
ask. Pushing, publishing packages, writing into a public directory. A single confirmation stops most of what goes wrong - Keep
allowminimal at the start.git status,git diff, and your test command are usually enough for the first day - When you hit a wall, open
/permissionsand add exactly one rule. No restart required anymore
Steps 3 and 4 only became practical with the mid-turn change. Until now, "I do not want to interrupt my work" pushed everyone toward granting broad access up front, and that broad access stayed. Now the grant can happen at the moment of actual need, at the size of the actual need.
If the matcher syntax itself is the unfamiliar part — how patterns like Bash(npm run test:*) are written — the piece on building a tool permission policy from scratch lays out the basic shapes.
Three things to try before restarting
When something is blocked mid-task, this is the order I check:
- Open
/permissions. It opens during a response now. Look at which category the rule sits in — anything indenywill not be rescued by adding it toallow - Check whether you are reaching outside the working directory. If so,
/add-dirhandles it without leaving the session - Compare the pattern against the command that actually ran. Writing
Bash(npm test:*)while your project usesnpm run testis a mismatch I have created more than once
Only when none of the three helps do I consider restarting. Previously I jumped straight to a restart, so the list is exactly three steps longer than it used to be — and considerably cheaper.
One thing to do today
Before you close today's session, open /permissions and read the list once, especially if you clicked Don't ask again at any point. If even one rule is there that you cannot explain, that rule is your starting point.
My own inventory turned up an allowance I had added half a year ago for a project I no longer work on. Finding something like that is not evidence of a careless setup — it is evidence that you kept working instead of stopping, which was the right call at the time. It just deserves a second pass now that a second pass is cheap.