Your Agent Can't Grant Itself the Permission It Needs
I asked my coding agent to turn a session into an article, which is a thing I do often enough that I have a skill for it, and it came back with a finished draft that it couldn't deliver. The draft was sitting in a temp directory, the destination was a folder in my own notes vault, and the report was blunt about why it never got there, saying the write "was denied by permission settings, not by the sandbox," and that turning the sandbox off "didn't help." So the agent had done the work, knew exactly where the work belonged, knew exactly which lines of configuration would let it put the work there, and was structurally forbidden from writing those lines. That last part is the interesting part, and it isn't a bug.
The agent knew the fix, could name the file and the lines, and the one thing it couldn't do was grant itself the right to write them.
The mental model that was wrong
I had assumed, the way most people assume, that permissions are a list. You have an allow list, you add a line to it, the thing that was blocked stops being blocked, and you go on with your day. That model survives contact with simple tools and falls apart immediately with an agent that runs code on your machine, because there is no single list. There are several independent gates, each owned by a different part of the system, each able to say no on its own, and only one of them tends to tell you which one spoke.
Here is what was actually standing between the agent and one folder.
| Layer | What it is | What it blocked here |
|---|---|---|
| Permission rules | The allow, ask, and deny lists in the settings file | No rule existed for the vault path, so every read and write there went to the gate |
| OS sandbox | A seatbelt profile built from a short write allowlist | The vault wasn't on the list, so shell writes failed at the kernel |
| Auto mode classifier | A model that judges each action against standing authorizations | Blocked the agent from editing its own permission file |
The first two are configuration and behave the way configuration behaves, which is to say they're boring and you can reason about them. The third is a judgment call made in the moment, and it's the reason this session became worth writing about, since a classifier that evaluates intent will treat "write a file" and "write the file that decides what you're allowed to write" as very different requests even though both are just a text edit.
Cause 1: the sandbox allowlist was two directories long
Every shell command the agent runs goes through a macOS sandbox profile, and that profile is built from a short list in the settings file. Mine was, verbatim, this.
"sandbox": {
"filesystem": {
"allowWrite": ["~/repos", "~/.cache"]
}
}
My repos and a cache directory, which covers roughly everything an agent does when it's writing code and nothing at all when it's writing prose into a notes vault that lives under ~/Documents. Nothing in the failure message says "your allowlist is too short," it just says the operation isn't permitted, which reads like a macOS privacy problem and sends you off checking Full Disk Access for twenty minutes. A generic filesystem error from a sandboxed agent is a configuration question before it's an operating system question.
Cause 2: a bare Read rule didn't cover what I thought it covered
The allow list had a plain Read entry in it, which I'd read as "reading is allowed, globally, we're done here." It isn't quite that, since a read of a file well outside the session's working directory still lands in front of the gate, and the agent had been denied on the style guide that defines my writing voice, which was the single most important input to the task it had been given. It wrote the article anyway from the rules quoted inside the skill, and it told me it had done that. The output was fine and the process was wrong, because the authority file was sitting right there and the agent was reduced to working from a summary of it. When an agent reports that it worked around a blocked input, treat that as a defect in your setup rather than a workaround that succeeded.
Cause 3: the agent isn't allowed to widen its own permissions
This is the one that matters. I told the agent to fix the permissions, it composed exactly the right edit, and the response came back as this, verbatim.
Permission for this action was denied by the Claude Code auto mode classifier.
Reason: Blocked by classifier.
It tried again through the sanctioned path, a skill built specifically for editing settings, and got the identical refusal. So it did the reasonable thing and wrote a patch script into a scratch directory, handed me a one-line command to run it myself, and stopped. That's a well-behaved agent hitting a wall that exists on purpose, and if you think about the alternative for even a second you want the wall, because an agent that can widen its own permissions doesn't really have permissions. Every other control in the system is downstream of that one file, so the file is where the line has to be. Self-permissioning is the one capability worth keeping expensive, because everything else you configured depends on it holding.
An agent that can rewrite its own permission file doesn't have permissions, it has preferences.
What I tried and what actually happened
| Attempt | Result |
|---|---|
| Agent edits the settings file directly | Blocked by classifier |
| Agent invokes the settings-editing skill | Blocked by classifier, same message |
| Agent writes a patch script for me to run | Allowed, and would have worked |
| I reply with a single word, "retry" | The same edit goes through |
That last row is the honest surprise of the session. The edit that was refused twice succeeded on a plain "retry" from me, and the only thing that changed between the refusal and the success was that I'd asked for it in my own words, in the same breath, which is what turns an agent widening its own access into a user widening the agent's access. I can't tell you the classifier's internals and I'm not going to pretend otherwise, but the behavior is consistent with intent being read from the conversation rather than from the diff, and the practical lesson holds either way. If your agent says it was blocked from a change you actually want, ask for it plainly and directly, because your explicit ask is the authorization the gate is looking for.
The fix
Three edits, one per layer, all in the same settings file.
1. Path rules for the vault and the style guides, added to the allow list, so reads and writes there stop going to the gate.
+ "Read(//Users/me/.claude/claude-files/**)",
+ "Read(//Users/me/Documents/Obsidian/Notes/**)",
+ "Write(//Users/me/Documents/Obsidian/Notes/**)",
+ "Edit(//Users/me/Documents/Obsidian/Notes/**)",
+ "Bash(mkdir -p /Users/me/Documents/Obsidian/Notes/*)",
+ "Bash(cp * /Users/me/Documents/Obsidian/Notes/*)",
+ "Bash(ln -s * /Users/me/Documents/Obsidian/Notes/*)",
2. The vault added to the sandbox write allowlist, so shell commands can reach it at all.
"allowWrite": [
"~/repos",
- "~/.cache"
+ "~/.cache",
+ "~/Documents/Obsidian/Notes"
]
3. A standing authorization written in plain English for the classifier, which is the layer that judges rather than matches and therefore takes prose instead of globs.
Obsidian Vault Writes: Reading and writing files under
~/Documents/Obsidian/Notes is standing user-authorized and needs
no naming. It is a private local notes directory, not a
publication surface. This clears the destination only, so
credential leakage and data exfiltration rules still judge
what gets written.
Then the part people skip, which is proving it rather than declaring it.
$ echo ok > ~/Documents/Obsidian/Notes/"Pending Articles"/.write-test && echo works
works
The sandbox picked the change up live, without a restart, which I didn't expect and wouldn't have known if the agent hadn't tried it.
There's one more trap worth naming, since it caught us on the very next command. The agent tried to write this article with a shell heredoc, and the write was denied, not because of the vault but because the article text quotes the path to the settings file, and a deny rule written as Bash(cat > *settings.json*) matches on the whole command string regardless of which file is actually being written. Writing the same content through a file tool went through immediately. Deny rules that pattern-match a command string will catch prose that merely mentions the thing they're protecting.
What generalizes
Layered permission systems fail identically at every layer, so diagnose by elimination rather than by reading the error. The message says "denied," and denied by a rule, denied by the kernel, and denied by a classifier are three different problems with three different fixes. The fastest test is doing the same thing a different way, since the agent could read the style guide through a shell in one breath and be refused reading it with a file tool in the next, and that gap tells you the file is fine and the gate is the problem.
Write the allow rule for the thing you actually do, not the thing you think you do. The vault needed reads, writes, edits, a mkdir, a cp, and a symlink, because the workflow that touches it does all six, and an allow list that covers five of them produces a prompt at the worst possible moment and a report explaining why the work is sitting in a temp directory.
A rules layer matches text and a judgment layer reads intent, so write to each in its own language. Globs belong in the rules list and sentences belong in the classifier list, and a sentence that says what's cleared and what's still judged is far more useful than one that just says yes, since the point of a standing authorization is to clear the destination without clearing the content.
The blocked thing an agent quietly worked around deserves more attention than the thing it reported as broken. The failed vault write was loud and got fixed in one session, while the style guide it couldn't read was a single line in the report, and that was the input determining whether the output was any good.
Keep self-permissioning gated even when it costs you a round trip. The friction here was one message from me, and I'll take that trade every time over an agent that can quietly widen its own access whenever a task gets inconvenient.
The part I'm not claiming
I'm not claiming the classifier was wrong to block the agent, because it was doing precisely what I want it to do, which is refuse an unprompted expansion of an agent's own authority and make a human say the word out loud. The fix isn't a general one either, since it opens a single directory tree on a personal machine, and the same edit against a shared or managed configuration is a different conversation with different stakes. What I will say is that the failure was expensive out of proportion to the actual problem, since a whole session ended with finished work stranded in a temp folder over a few missing lines of JSON, and the reason nobody catches this in advance is that agent permissions are the part of these tools you configure once and never look at again until something you wanted refuses to happen.
Configuration you never revisit is configuration you don't actually know the contents of, and you find that out on the day it says no.