Claude Code 权限配置指南:settings.json 与 6 种模式(2026)
用 Claude Code 的人几乎都会撞上同一堵墙:不配权限,它每跑一条命令就弹一次确认框,长任务根本没法自动跑;直接上 --dangerously-skip-permissions,又等于把 rm、git push、发部署脚本的权力全交出去。正确答案在中间——花二十分钟把 settings.json 的权限规则配明白,让读操作和测试命令自动放行、让改动性操作照样问你、让危险操作彻底禁掉。这篇 Claude Code 权限配置指南把规则语法、评估顺序、六种权限模式和那些不看文档一定会踩的坑一次讲清楚,最后给一份可以直接抄的起步配置。
权限系统在管什么
Claude Code 把工具分成三类,默认策略各不相同。理解这张表,你就知道为什么有些操作从来不问你、有些每次都问:
| 工具类型 | 举例 | 默认是否需要批准 | 选「不再询问」后的有效期 |
|---|---|---|---|
| 只读 | 文件读取、Grep | 否(在工作目录及已添加目录内) | 不适用 |
| Bash 命令 | Shell 执行 | 是,内置只读命令集合除外 | 对该项目目录 + 该命令永久有效 |
| 文件修改 | Edit / Write | 是 | 到本次会话结束 |
关键认知:规则由客户端强制执行
权限规则是 Claude Code 程序层面执行的,不是模型自觉遵守的。你在 CLAUDE.md 里写「不要执行 git push」只能影响模型想不想做,改变不了它被允许做什么。要真正卡死,必须用权限规则、权限模式或 PreToolUse hook。
另外提一个很多人不知道的小功能:在 Bash 或 PowerShell 的权限提示框上按 Ctrl+E,Claude Code 会解释这条命令是干什么的、为什么要跑、可能出什么问题,并标上低/中/高风险。这个解释只在你按键时才发给模型,不会每次提示都消耗 token。
settings.json 放在哪:四个位置和它们的优先级
Claude Code 的配置是分层合并的,同一个 permissions 字段可以出现在多个文件里。先记住位置:
| 层级 | 路径 | 用途 | 是否该提交 git |
|---|---|---|---|
| 用户设置 | ~/.claude/settings.json(Windows:%USERPROFILE%\.claude\settings.json) | 跨所有项目的个人偏好 | 不涉及 |
| 项目设置 | <项目根>/.claude/settings.json | 团队共享的项目规则 | 应该提交 |
| 本地项目设置 | <项目根>/.claude/settings.local.json | 只属于你的个人覆盖 | 应该 gitignore |
| 托管设置 | macOS:/Library/Application Support/ClaudeCode/managed-settings.json;Linux/WSL:/etc/claude-code/managed-settings.json;Windows:C:\Program Files\ClaudeCode\managed-settings.json | 企业统一下发的策略 | 由 IT 部署 |
优先级从高到低是:托管设置 → 命令行参数 → 本地项目设置 → 项目设置 → 用户设置。托管设置连命令行参数都盖不过,这是企业管控的基础。
deny 是跨层级贯穿的
任何一层拒绝了某个工具,其它层都无法重新允许它。用户设置里的 deny 会挡住项目设置里的 allow,反之亦然。所以想给自己上保险,把 deny 写在用户设置里最稳。
三类规则:allow / ask / deny 与评估顺序
权限规则写在 permissions 下的三个数组里,含义很直白:allow 是自动放行,ask 是每次都问,deny 是永远拒绝。真正容易搞错的是评估顺序:
- 1先匹配 deny,命中即拒绝
- 2再匹配 ask,命中即提示
- 3最后匹配 allow,命中即放行
- 4第一个命中的规则决定结果,规则写得多具体都不改变这个顺序
deny 不能开例外,这是最常见的误解
写了 deny 规则 Bash(aws *),再写 allow 规则 Bash(aws s3 ls) 是无效的——aws s3 ls 同时命中两条,而 deny 先被评估,所以照样被拒。ask 和 allow 之间也一样:命中的 ask 规则会盖过更具体的 allow 规则。想「大体禁止、个别放行」,只能反过来写:不写宽泛 deny,而是精确地只 allow 你要的那几条。
还有一个行为差异值得注意:deny 规则写裸工具名(如 "Bash")会把这个工具从模型的上下文里整个移除,模型压根看不见它;而写带范围的模式(如 "Bash(rm *)")会保留工具可用,只在模型尝试匹配调用时拦下来。
规则语法速查
规则格式是 Tool 或 Tool(specifier)。不带括号就是匹配这个工具的所有用法(Bash(*) 等同于 Bash)。各工具的说明符语法不一样,这是最容易写错的地方:
| 工具 | 语法 | 示例 | 说明 |
|---|---|---|---|
| Bash | 命令 + `*` 通配符 | Bash(npm run test *) | `*` 可出现在任意位置,能跨越空格 |
| PowerShell | 同 Bash,别名会被规范化 | PowerShell(Get-ChildItem *) | 写 cmdlet 名也能匹配 gci/ls/dir,大小写不敏感 |
| Read / Edit | gitignore 风格路径 | Read(./.env)、Edit(/src/**/*.ts) | 锚点规则见下节,很容易踩坑 |
| WebFetch | domain: 前缀 | WebFetch(domain:*.example.com) | 匹配主机名,大小写不敏感 |
| MCP | mcp__服务器__工具 | mcp__puppeteer__* | allow 规则里服务器段不能带通配符 |
| Agent | 子智能体名 | Agent(Explore) | 写进 deny 即可禁用某个子智能体 |
| 按参数匹配 | Tool(参数:值) | Bash(run_in_background:true) | 仅 deny/ask 可用,且只能匹配顶层标量参数 |
Bash 通配符:那个空格很要命
* 前面有没有空格,匹配结果完全不同。有空格会强制单词边界,没空格则不会:
Bash(ls *)匹配ls -la,不匹配lsofBash(ls*)两个都匹配(因为没有单词边界约束)Bash(ls:*)是Bash(ls *)的等价写法,:*后缀只在模式末尾被识别Bash(git * main)能匹配git checkout main,也能匹配git push origin main——一个*可以跨多个参数
文件路径:一个斜杠和两个斜杠不是一回事
Read 和 Edit 规则遵循 gitignore 规范,有四种锚点,写错了规则就锚到别的地方去了:
| 写法 | 含义 | 示例 | 实际匹配 |
|---|---|---|---|
| //path | 文件系统根目录的绝对路径 | Read(//Users/alice/secrets/**) | /Users/alice/secrets/** |
| ~/path | 主目录相对路径 | Read(~/Documents/*.pdf) | 主目录下的 Documents |
| /path | **相对于设置文件所在位置** | Edit(/src/**/*.ts) | 写在项目设置里 = <项目根>/src/** |
| path 或 ./path | 相对于当前目录 | Read(*.env) | <当前目录>/*.env |
单斜杠不是绝对路径
Read(/secrets/**) 写在用户设置 ~/.claude/settings.json 里,挡的是 ~/.claude/secrets/**,而不是你项目里的 secrets 目录,更不是 /secrets。要写真正的绝对路径必须用双斜杠 //。想在用户设置里写一条对所有项目都生效的规则,用 // 或 ~/ 开头。
裸文件名遵循 gitignore 语义、在任何深度匹配,所以 Read(.env) 和 Read(**/.env) 是等价的,都挡当前目录及其下所有 .env,但挡不住父目录或别的项目里的。Windows 上路径会先规范化成 POSIX 形式,C:\Users\alice 变成 /c/Users/alice,所以匹配全盘 .env 要写 //**/.env。
六种权限模式,分别什么时候用
规则管的是「哪些调用放行」,模式管的是「没有规则命中时怎么办」。在设置文件里用 permissions.defaultMode 指定,或启动时用 --permission-mode 覆盖,交互式会话里按 Shift+Tab 循环切换:
| 模式 | 行为 | 适用场景 |
|---|---|---|
| default(别名 manual) | 每个工具首次使用时提示 | 默认档,不确定就用它 |
| acceptEdits | 自动接受工作目录内的文件编辑和 mkdir/touch/mv/cp 等常见文件命令 | 已经确定方案、要它连续改一批文件时 |
| plan | 只读文件、只跑只读命令来探索,不改源文件 | 让它先看懂代码再动手,强烈推荐先跑一轮 |
| auto | 自动批准,后台有安全检查验证操作是否与你的请求一致 | 长任务想少被打断又不想裸奔 |
| dontAsk | 自动拒绝,除非已被 allow 规则预先批准 | 严格白名单场景 / 自动化流水线 |
| bypassPermissions | 跳过几乎所有提示 | 只在容器 / 虚拟机等隔离环境用 |
bypassPermissions 到底跳过了什么
它连对 .git、.claude、.vscode、.idea、.husky、.cargo、.devcontainer 等目录的写入都不再提示——也就是说它可以改你的 git 配置和 Claude Code 自身配置。只在容器或虚拟机里用。少数情况仍会提示:显式 ask 规则、组织设为 ask 的连接器工具,以及针对文件系统根目录或主目录的删除(如 rm -rf / 和 rm -rf ~)会作为断路器拦一下。
命令行等价写法是 claude --dangerously-skip-permissions,等同于 --permission-mode bypassPermissions。如果你想默认从 plan 模式起步、但保留随时切到 bypass 的能力,用 --allow-dangerously-skip-permissions——它只是把 bypassPermissions 加进 Shift+Tab 的循环里,不会立刻启用:
# 从 plan 模式起步(推荐的日常习惯)
claude --permission-mode plan
# 起步于 plan,但允许后面手动切到 bypass
claude --permission-mode plan --allow-dangerously-skip-permissions
# 临时覆盖工具白名单(单次会话)
claude --allowedTools "Bash(git log *)" "Bash(git diff *)" "Read"
# 临时禁用某些工具;裸名会把工具从模型上下文里移除
claude --disallowedTools "Edit" "mcp__*"想从组织层面禁掉危险模式,在任意设置文件里把 permissions.disableBypassPermissionsMode 或 permissions.disableAutoMode 设成 "disable"。写在托管设置里没人能覆盖;写在自己的用户设置里则相当于给自己上把锁,防手滑。
一份可以直接抄的起步配置
原则是:读和验证类操作放行、改动意图的操作照问、密钥和不可逆操作彻底禁掉。下面这份放进项目的 .claude/settings.json,按你的技术栈改改命令名就能用:
{
"permissions": {
"defaultMode": "default",
"allow": [
"Read",
"Bash(npm run lint)",
"Bash(npm run test *)",
"Bash(npm run build)",
"Bash(git status *)",
"Bash(git diff *)",
"Bash(git log *)"
],
"ask": [
"Bash(git commit *)",
"Bash(npm install *)",
"Write(./config/**)"
],
"deny": [
"Read(.env)",
"Read(.env.*)",
"Read(./secrets/**)",
"Read(~/.ssh/**)",
"Read(~/.aws/**)",
"Bash(git push *)",
"Bash(rm -rf *)",
"Bash(curl *)",
"Bash(wget *)"
],
"additionalDirectories": [],
"disableBypassPermissionsMode": "disable"
}
}为什么 deny 里要挡 curl 和 wget
只要 Bash 可用,模型就能用 curl / wget 访问任意 URL,绕过你精心配置的 WebFetch 域名白名单。想控制网络访问,正确姿势是 deny 掉这些命令行网络工具,再用 WebFetch(domain:xxx) 放行允许的域名。想要 OS 级别的强制隔离,还得开沙箱。
配完后在会话里跑 /permissions,它会列出所有生效的规则以及每条来自哪个 settings.json 文件——排查「为什么这条规则没生效」时非常好用。
四个不看文档一定会踩的坑
1. 复合命令要每一段都匹配
Claude Code 认识 shell 运算符,&&、||、;、|、|&、& 和换行都会把命令切成子命令,规则必须独立匹配每一段。所以 Bash(safe-cmd *) 不会给它权限跑 safe-cmd && other-cmd。反过来,当你对复合命令选「不再询问」时,它会为每个需要批准的子命令各存一条规则(单条复合命令最多存 5 条),而不是把整串存成一条。
2. 进程包装器有的会剥离、有的不会
匹配前 Claude Code 会剥掉一组固定的包装器:timeout、time、nice、nohup、stdbuf,以及不带标志的 xargs。所以 Bash(npm test *) 也能匹配 timeout 30 npm test。但这个列表是内置的、不可配置——npx、docker exec、direnv exec、devbox run、mise exec 都不在列表里。这意味着 Bash(devbox run *) 会匹配 run 后面的任何东西,包括 devbox run rm -rf .。要放行环境运行器里的工作,必须把运行器和内部命令一起写死,比如 Bash(devbox run npm test)。
另外 watch、setsid、ionice、flock 这类 exec 包装器永远会提示,Bash(watch *) 这种前缀规则自动批准不了;带 -exec 或 -delete 的 find 同理。
3. 想用参数约束 URL 是徒劳的
很多人写 Bash(curl http://github.com/ *) 想把 curl 限制在 GitHub,实际上一堆变体都绕得过去:curl -X GET http://...(URL 前有选项)、curl https://...(换协议)、curl -L http://bit.ly/xyz(重定向过去)、URL=http://github.com && curl $URL(用变量)、甚至多打一个空格。约束命令参数这条路本身就是脆的,老老实实 deny 掉 curl,用 WebFetch 的域名规则或 PreToolUse hook 来管。
4. 项目里的 allow 规则需要「工作区信任」
项目 .claude/settings.json 里的 permissions.allow 和 additionalDirectories 是在授予能力,所以 Claude Code 只有在你接受了该工作区的信任对话框之后才应用它们——在那之前规则读了但不生效。deny 和 ask 不受影响,因为它们只做限制。这也是为什么你 clone 一个仓库后发现它的 allow 规则「没反应」。顺带一提:在非交互模式(-p)下不会弹信任对话框,规则会一直被忽略,写自动化脚本时要留意。
权限、Hooks 与沙箱的分工
这三层经常被混为一谈,实际职责很清楚:
- 权限规则 —— 静态配置,管「哪些工具调用放行」,覆盖所有工具(Bash、Read、Edit、WebFetch、MCP)
- PreToolUse hook —— 动态判断,在权限提示之前跑你的脚本,可以拒绝、强制提示或跳过提示。但 hook 决定不能绕过 deny 和 ask 规则;以退出码 2 退出的阻断 hook 则优先于 allow 规则
- 沙箱 —— OS 级强制隔离,只作用于 Bash 命令及其子进程,能挡住权限规则挡不住的东西(比如 Python 脚本自己打开文件读写)
值得单独强调一点:Read / Edit 的 deny 规则只覆盖 Claude 的内置文件工具和它认识的 cat、head、tail、sed 等命令,不覆盖任意子进程——模型写个 Python 脚本去读 .env 照样读得到。要真正锁死某个路径,得开沙箱做 OS 级强制。
把权限配明白,Claude Code 才真正从「一步一确认的玩具」变成能托付长任务的工具——这也是从「会用 AI」到「用得高效」之间最实打实的一道门槛。权限之外,CLAUDE.md 怎么写、Hooks 怎么串自动化、子智能体怎么分工,都是同一套工程化思路的延伸。想系统地把这套方法学下来,欢迎来 IMAI 看我们的实战课程。
想系统学会用 AI 编程,从入门到做出真实产品?
查看系统课程