1 它停下来,先询问
你在 ~/work/demo-api 里让 Claude Code 跑测试,执行 npm test 之前,它没有直接动手——先弹出一个确认框:这条命令是什么、要不要放行。
你选了yes。下一轮它再跑 npm test,弹窗没再出现,命令直接执行。
可同一台机器上,另外两件事完全不同:读项目里的文件,从来不弹窗;改某些配置文件,点了「不再询问」,下次照样弹。
为什么跑 npm test 要询问、读文件不用询问、有的连选择了「不再询问」都不行?这篇拆 Claude Code 的权限系统。
2 三种答案,不是两种
先看答案有几种。源码里有一张清单,列下规则能给出的全部行为:
const SUPPORTED_RULE_BEHAVIORS = ['allow', 'deny', 'ask'] as const
放行、拒绝、询问。多数人以为权限就是「允许 / 不允许」两个格子,第三个“询问”也是一种,而且是写进规则语法里的正式答案:你可以专门写一条规则,要求某类操作永远弹窗。
| 答案 | 它做什么 | 什么时候出现 |
|---|---|---|
| allow(放行) | 不询问,直接执行 | 命中写下的放行规则;读工作目录里的文件 |
| deny(拒绝) | 不询问,直接拦下 | 命中拒绝规则;动作被组织策略禁掉 |
| ask(询问) | 弹窗,人来拍板 | 命中「必须询问」的规则;没有任何答案时的默认 |
「询问」不是没写规则时的临时办法,它和放行、拒绝一样,是三种答案之一。
为什么非要三种?假设只有放行和拒绝两格,一个没写规则的操作只能二选一:默认放行——危险;默认拒绝——什么都干不了,烦人。第三种答案把「这类事我说了不算,人说了算」变成一条可以写下来的规则。比如:发布包的命令(npm publish)永远要询问——哪怕这个会话里别的都放行了。
规则长什么样?三种答案共用一套语法,写在配置文件里:
Bash(npm test:*) // 前缀规则: 所有以 npm test 开头的命令
Read(./docs/**) // 文件规则: docs 目录下的全部文件
Bash // 危险写法: 不带括号, 等于整个 Bash 工具全放行
第三行是个经典陷阱。想写「跑命令不用询问」,顺手写了个单独的 Bash——解析器眼里它等于「Bash 工具、所有命令」全部放行。源码注释给这类规则定了性:效果等同于绕过权限模式。你心里想的是「跑 npm 不用询问」,实际写下的是「任何命令都不用询问」——包括 rm -rf。
3 权限模式:会话级的总开关
规则是事先写下的回答。在规则之外还有一个会话级的总开关——权限模式,按 Shift+Tab 轮着切:
| 模式 | 干什么 |
|---|---|
| default(默认) | 没有规则替它回答的事,都要询问 |
| acceptEdits(自动接受编辑) | 改工作目录里的文件不再问,其余照旧 |
| plan(计划) | 只读不写,改动整个关掉 |
| bypassPermissions(绕过权限) | 没有规则替它回答的事,直接放行 |
分工一句话:模式定「没人说话时的默认答案」,规则定「具体某类操作怎么办」。规则是例外,两头都成立:
- default 模式里,一条
Bash(npm test:*)让测试不询问就跑; - acceptEdits 模式里,一条「必须询问」规则命中了,照样弹窗。
acceptEdits 放不放行,看文件在哪个目录:工作目录里面的改动放行,外面的照旧要询问。工作目录就是你启动 Claude Code 的那个目录,加上你手动加进来的目录。
bypassPermissions 听着像「全部放行」,但有两件事经常被误会:
-
它改的只是默认答案——把「没写规则时询问」改成「没写规则时放行」。写下的规则照样生效:拒绝规则拦得住,「必须询问」规则也拦得住。源码注释写得很直白:这类规则必须照常尊重,就像拒绝规则一样。
-
它自己可以被关掉。组织策略里留了一条开关,本机设置也能关:
Bypass permissions mode was disabled by your organization policy
4 一个动作来了,先查什么、后查什么
模式、规则,再加上一道目录边界——一个动作来了,就按这三层从头查到尾:
plan 模式不进这张图——它在第一层就把改动整个关掉了,链不往下走。图里还有三个值得停下来问的地方。
为什么拒绝永远排第一?
决策链的第一步永远先查拒绝。源码注释写明:拒绝检查必须先于一切放行检查。设想两条规则冲突——一条「Bash 全放行」,一条「rm 开头的命令必须拒绝」:如果先查放行,拒绝永远轮不到出场。宁可拦错,不可放过——顺序本身就是安全设计。
为什么改 .bashrc、.git/这类文件,写了放行规则也照样弹窗?
图里第二层那条岔路,就是改文件的动作要先过的一道敏感文件检查——看要改的是不是这批文件:
.gitconfig .bashrc .zshrc .profile .mcp.json .claude.json ...
.git/ .vscode/ .idea/ .claude/
这批文件都是改了会影响系统行为的配置:git 的、shell 的、Claude Code 自己的。名字又太常见,正常干活总会碰到(.git/ 目录每次提交都在动)。
源码注释写明,这道检查必须排在放行规则之前,防止一条宽泛的放行规则,顺手把改这些文件的许可也放行了。三个平时能免掉弹窗的办法,到这儿全部失效:
- 写了放行规则——照样弹窗;
- 点了「不再询问」——下次照样弹;
- 开了绕过权限模式——照样弹窗。
源码给它的定性是 bypass-immune。「敏感文件面前,放行规则说了不算」,是这套系统里少数不许配置让路的地方。
允许改的文件,为什么可能读不了?
有一条便利规则:允许改的文件,默认也允许读,能改总得先能看。但专门写的「拒绝读」规则排在便利前面。比如一个存着密钥的文件,你允许 Claude 改、又写了条规则拒绝它读,它改得了,读不了。写的允许,赢不了读的拒绝。
5 你点的「不再询问」去了哪
开场你点的那下「不再询问」,默认写进 .claude/settings.local.json,这个项目的本地配置。它和 .claude/settings.json 的关键区别:local 这份不进 git,你个人本地用。
写下的规则,有两种等于没写。
**一种出在复合命令上。**看这条:
cd src && git status && npm test
弹窗是 npm test 触发的,前两段只读、不用批。你点「不再询问」,老版本取整条命令的开头两个词,记成 Bash(cd src:*),只放行 cd src 开头的命令。但是 npm test 还是没人放行,弹窗照样来,再点一次,又多一条没用的规则,有用户攒了很多条这样的死规则。现在的版本改按段记:命令拆成几段,建议按段给,弹窗里勾哪段、记哪段。
**另一种是被覆盖了。**你写了条具体的 Bash(ls:*) 放行,配置里又有一条单独的 Bash 的「必须询问」,什么命令都先命中那条「必须询问」,轮不到具体的 Bash(ls:*)。现在的版本能查出来,两条里删一条就行:
- 删掉那条「必须询问」;
- 或者删掉
Bash(ls:*)。
6 把「该不该」留给人
开场三个疑问,现在都有了答案。
- 跑
npm test为什么要询问?第一次跑,没有规则替它回答,默认答案是询问。 - 读项目文件为什么不用询问?第三层目录边界直接放行。
- 有的文件为什么点了「不再询问」也照样弹?敏感文件检查排在放行规则之前。
「询问」不是系统没了主意,规则一次写好、以后照用,询问是每一次都停下来,把「该不该」留给人。
小技巧:权限弹窗出现时按 ctrl+e,会有一个小模型当场解释这条命令在干什么、风险多高——解释失败也不耽误你正常选择。
这篇讲的是一个动作怎么判定。下一篇拆规则本身:一条命令来了,系统怎么知道该用哪条规则?写在几个配置文件里的规则冲突了,听谁的?今天记下的规则,下次打开还在不在?
连载《拆解 Claude Code》· 关注不迷路