Claude CodeLesson
Claude Code permissions
Claude Code asks before it runs most tool calls. Permission rules in settings files decide which calls skip the prompt, which always prompt and which never run; the permission mode decides every call no rule settles. Writes to a set of Claude Code's own paths are never pre-approved by any rule, whatever the files say.
Know Claude Code permissions already? Skip to In Terrarium.
Outside the box · General
The concept
Claude Code reads settings from several files. Highest first, they are an organization's managed settings, JSON passed with --settings on the command line, your uncommitted .claude/settings.local.json, the project's committed .claude/settings.json, and your user file ~/.claude/settings.json. A key set higher overrides the same key lower down, but a list such as permissions.allow merges across the files. A cloud session reads fewer of them, as Cloud sessions shows. Besides permissions, a settings file can set env variables and hooks.
Permission rules name a tool, optionally narrowed to a command or path:
{
"permissions": {
"allow": ["Bash(npm test *)"],
"ask": ["Bash(git push *)"],
"deny": ["Read(./.env)"]
}
}
An allow rule lets a call run without a prompt, ask always prompts, and deny blocks. Claude Code checks deny first, then ask, then allow, and the first match decides, so a narrow allow can't carve an exception out of a broad deny, and a deny in any file blocks what another file allows. A project's allow rules apply only once you accept the folder's trust dialog; its deny and ask rules apply at once, since they only restrict.
The permission mode sets what happens to a call no rule settles. default prompts on the first use of each tool; acceptEdits also accepts file edits and common file commands inside the working directory; plan explores without editing; auto has a classifier model check each action instead of asking; dontAsk denies whatever would prompt; and bypassPermissions skips prompts, where allow rules have no effect but a deny still blocks and an ask rule still prompts. defaultMode in a settings file sets the mode a session starts in.
Writes to Claude Code's protected paths, such as .git, .vscode, .mcp.json and most of .claude, are never auto-approved, except in bypassPermissions and in some interactive plan-mode sessions. No allow rule changes that: the safety check runs before the allow rules, so an Edit(.claude/**) entry does nothing. What happens instead depends on the mode: default and acceptEdits prompt, auto sends the write to the classifier, and dontAsk denies it.
A PreToolUse hook runs before the rules and can deny a call, force a prompt or let it run. A hook that blocks stops the call even when an allow rule matches it, but a hook's allow never skips a deny or ask rule; Blocking hooks shows such a hook.
Full referenceClaude Code docs: Configure permissions
In Terrarium
The committed .claude/settings.json holds Terrarium's only permission rules: a short allow list, with no ask or deny rule and no defaultMode. No .claude/settings.local.json is committed. The list does not always apply: docs/agents/environment-caveats.md records that an untrusted workspace drops permissions.allow before matching any rule, which is the trust rule above at work, and a cloud session can be such a workspace.
5 "permissions": {
6 "allow": [
7 "mcp__github__create_pull_request",
8 "mcp__github__pull_request_read",
9 "Workflow(clear-backlog-explore)",
10 "Workflow(clear-backlog-build)"
11 ]
12 },
The calls Terrarium refuses, it refuses with hooks. The same file wires each guard, Terrarium's name for a blocking hook, as a PreToolUse hook on the tools it checks. This one denies a main session's Agent, Workflow or SendMessage call until the session has run or read the dispatch-subagents Skill:
34 {
35 "matcher": "Agent|Workflow|SendMessage",
36 "hooks": [
37 {
38 "type": "command",
39 "command": "cd \"${CLAUDE_PROJECT_DIR:-.}\" && node_modules/.bin/tsx scripts/hooks/dispatch-read-guard.ts",
40 "onFailure": "block"
41 }
42 ]
43 },
With no defaultMode in the repo, the mode comes from where a session starts: a person picks a cloud session's mode when creating it, and a 's run starts in auto mode (Scheduled tasks). Which parts of the file a cloud session reads depends on how it starts, as Cloud sessions shows.
Why it's built this way
- Keep the settings prompt. An unattended run's subagent stalled for hours on the prompt to edit .claude/settings.json. Pre-approving the edit wouldn't help in a cloud session, which can drop
permissions.allow, and would let an agent switch off its own guards. So the prompt stays, and no unattended session is sent to change a guard. The cost: such a fix waits for a session someone attends. .out-of-scope/pre-approved-settings-edits.md. - A short allow list. MCP servers says why the committed list pre-approves opening a pull request and reading it, and what that costs.
What to watch for
- No allow rule makes settings edits silent.
.claude/is one of Claude Code's protected paths, so even an allow rule for it leaves an edit to .claude/settings.json checked. In a mode that prompts, an unattended run has nobody to answer; in auto mode the classifier decides. Blocking hooks says who merges a guard change instead. - An allow entry may do nothing in the cloud. Before you add one to silence a prompt, read why MCP servers says not to.
Try it in your project
Add "allow": ["Bash(git push origin *)"] and "ask": ["Bash(git push *)"] to your project's .claude/settings.json, then ask Claude to push a branch: it prompts, since ask is checked before allow. Then add Edit(.claude/**) to the allow list, start a session in default mode and ask Claude to edit that settings file: it still prompts.
Where next
- LessonCloud sessions
- LessonBlocking hooks
- LessonMCP servers
- LessonClaude Code hooks
- LessonScheduled tasks
Last updated · Latest source on GitHub ↗