公司动态
ClaudeCode的计划模式和权限
我们把 Claude Code 装好、配好也写好了 CLAUDE.md。在使用的时候会遇到一个问题。给一个天气 App 加未来七天预报我一边想放手让它去改一边又怕它哪一步删错了文件或者顺手把我没让它碰的网络层也一起重构了。于是就来回纠结每步都点允许太烦索性全自动又不放心。这篇文章就来拆这个问题。它其实是两件事。第一动手之前能不能先看看它打算怎么干这就是计划模式。第二动手当中怎么把哪些能干、哪些得先问我、哪些绝对不许定成规则而且是你说了算的规则这就是权限系统。后一块也是最容易踩坑的地方空格、分隔符、路径前缀差一个字符你以为配好的规则其实根本没生效。我尽量把它讲准。文中说法以官方文档code.claude.com为准截至 2026 年中。一、计划模式先出方案不碰你的代码计划模式Plan mode做的事很简单让 Claude 只研究、只给方案不动你的源码。在这个模式下它可以读文件可以跑只读命令去摸清代码库最后给你一份完整的改动计划但不会编辑任何一个源文件。官方原话是 reads files and runs read-only shell commands to explore but does not edit your source files。这恰好对症不敢放手这个病。碰上大改动或者一个你自己都不熟的代码库先让它把方案摆出来你看看思路对不对、它打算动哪几个文件确认没问题再放它真改。怎么进入有三种方式# ① 会话里按 ShiftTab 循环切到 plan状态栏会显示当前模式 # ② 只想让某一条提问先走计划在前面加 /plan /plan 帮天气 App 加一个未来七天预报先别动手给我方案 # ③ 启动时就进 plan claude --permission-mode plan方案出来之后怎么走这一步很多人没留意。计划就绪时Claude 会把方案摆给你并列出五个选项让你挑下一步批准并切到 auto 模式继续它自己往下跑批准并切到 acceptEdits自动改文件但跑命令仍要问批准但每个改动都让你手动逐个确认不批准带着你的反馈继续打磨方案用 Ultraplan 在浏览器里进一步细化进阶先不展开注意第 4 个方案不满意不用推倒重来把意见直接甩给它接着改就行。而只要你批准了方案就等于退出计划模式、切到你选的那个权限档它才开始动手。要是你只想退出计划模式、什么都不批再按一次ShiftTab就行。还有两个顺手的小点。方案太长、想自己动两笔按CtrlG能把它在你默认编辑器里打开改完再让它执行。如果某个项目你希望一进去就是计划模式在.claude/settings.json里写一行就行{ permissions: { defaultMode: plan } }回到我那个七天预报。当时就是先/plan它给的方案里写清楚要动WeatherApi.java、ForecastResponse.java两个文件加一个v7/weather/7d的接口调用。我一看接口路径没错、也没去乱碰 UI 层才放它改。大改动、不熟的代码先扫一眼方案再放权比改完再回头 review 省事得多主动权也一直在你手里。二、放权的地基deny、ask、allow计划模式管的是动手前剩下的是动手当中的规则。Claude Code 的权限系统就三种规则你随时能用/permissions命令可视化地看和改它会把每条规则、以及规则来自哪个 settings 文件都列出来allow免确认直接用ask每次用都问你一下deny禁止根本不给用。关键在它们怎么裁决。官方原文一字不差是这样Rules are evaluated in order: deny, then ask, then allow. The first match in that order determines the outcome, and rule specificity does not change the order.翻成人话是三句按 deny、ask、allow 的顺序查第一个命中的规则就定了结果规则写得再具体也不改变这个顺序。第三句最容易栽跟头。不少人以为更具体的规则优先级更高其实不是。看官方那个例子你写了个宽泛的 denyBash(aws *)又写了条具体的 allowBash(aws s3 ls)心想除了列 S3别的 aws 命令都禁。结果aws s3 ls也被禁了因为 deny 先查、先命中。deny 规则里开不出白名单例外。ask 也一样命中了 ask 就一定弹窗哪怕后面有更具体的 allow 也拦不住。这个顺序跨文件也成立。deny 只要在任意一层出现企业托管、项目、用户别的层再 allow 也救不回来。所以想开例外靠的不是再补一条更具体的 allow而是一开始就别用太宽的 deny 把它拦死。三、规则怎么写裸工具名和带括号规则的格式就两种Tool或者Tool(具体内容)。不带括号的裸工具名匹配这个工具的所有用法。Bash匹配所有命令Read匹配所有读取WebFetch匹配所有联网抓取Bash(*)和Bash完全等价。这里有个差别不少人不知道尤其拿来做 deny 的时候得分清裸名 deny比如Bash会把这个工具从 Claude 的上下文里整个拿掉它根本看不到有这么个工具带范围的 deny比如Bash(rm *)工具还在只拦住匹配的那几种调用。所以我不想让它跑任何命令和我只想禁掉 rm是两种完全不同的写法别混。带括号就是加一个 specifier 做精细控制Bash(npm run build)只匹配这一条命令Read(./.env)匹配当前目录的 .envWebFetch(domain:example.com)匹配这个域名。简单记裸名管的是这个工具用不用带括号管的是具体哪些用法而且裸名 deny 是让工具直接消失不是拦截。四、Bash 匹配里一个空格的差别这一节和下一节是全篇最需要盯细节的地方。你以为写对了的 Bash 规则很可能因为一个空格、一个分隔符根本没照你想的生效。空格决定边界直接看官方原文The space before*matters:Bash(ls *)matchesls -labut notlsof, whileBash(ls*)matches both.Bash(ls *)*前面有个空格这个空格强制了一个词边界前缀后面必须跟空格或者到此为止。所以它匹配ls -la但不匹配lsof。Bash(ls*)没有空格也就没有边界lsof、lsblk这些 ls 开头的全被它圈进来。就一个空格放行的范围差出十万八千里。还有个等价写法:*后缀等同于空格加星号Bash(ls:*)和Bash(ls *)一个意思。但注意:*只有在结尾才认写成Bash(git:* push)中间那个冒号会被当成普通字符什么 git 命令都匹配不到。复合命令每一段都得单独过关老教程常这么吓你写了Bash(npm test)它跑npm test; rm -rf .就把你坑了。现在不是这样了。官方讲得明白Claude Code is aware of shell operators, so a rule likeBash(safe-cmd *)wont give it permission to run the commandsafe-cmd other-cmd.Claude Code 认识 shell 分隔符它认的有、||、;、|、|、和换行。碰上复合命令它会拆成一段段子命令每一段都得各自命中规则整条才放行。所以npm test rm -rf .不会因为npm test被允许就整条放过那个rm还是要单独过一遍权限。顺带说一句你对一条复合命令点Yes, dont ask again它存下来的是每个需要批准的子命令各一条规则比如批准git status npm test实际存的是npm test单次最多存 5 条。进程包装器会先被扒掉再匹配这条能解释一个常见的困惑我只写了Bash(npm test *)怎么timeout 30 npm test也直接过了因为匹配之前Claude Code 会先剥掉一组固定的进程包装器timeout、time、nice、nohup、stdbuf还有不带参数的xargs剥完再拿里头真正的命令去匹配。但要当心那些没在剥离名单上的运行器npx、docker exec、devbox run、direnv exec这些不剥。它们会把后面一整串都当命令执行于是Bash(devbox run *)就等于把devbox run rm -rf .也放行了。要用就写具体点Bash(devbox run npm test)。只读命令本来就不弹最后一个省心的地方有一组内置的只读命令任何模式下都直接跑、不弹窗——ls cat echo pwd head tail grep find wc which diff stat du cd还有 git 的只读形式。这些你不用专门写 allow。一句话ls *和ls*不是一回事复合命令逐段过关包装器会被先扒掉。规则光看着对不够得真能匹配上才算数。五、路径锚点/开头不是你以为的绝对路径管能读、能改哪些文件的 Read、Edit 规则路径写法照 gitignore 的规范来一共四种锚点。这里藏着一个头号陷阱先看表写法含义例子实际指向//path绝对路径从文件系统根Read(//Users/alice/secrets/**)/Users/alice/secrets/**~/path家目录起算Read(~/Documents/*.pdf)你家目录下的 Documents/path项目根相对Edit(/src/**/*.ts)项目根/src/**/*.tspath或./path当前目录相对Read(*.env)当前目录下的 .env陷阱就在第三行。官方专门加了句警告A pattern like/Users/alice/fileis NOT an absolute path. Its relative to the project root. Use//Users/alice/filefor absolute paths.以单个/开头指的是项目根目录不是系统绝对路径。想写真正的绝对路径得用两个斜杠//。这条我在第 2 篇提过这次把它放进完整的四锚点里因为太多人在这儿写错本想 deny 掉系统里某个绝对路径结果写成单斜杠规则悄悄指到了项目里一个根本不存在的路径等于白写。再补两个常用的细节。裸文件名按 gitignore 在任意层级匹配Read(.env)等价于Read(**/.env)当前目录连同所有子目录下的 .env 都算而Read(//**/.env)才是匹配整个文件系统里的所有 .env。另外Read/Edit 的 deny 也会管住 Claude 在 Bash 里识别出来的cat、head、sed这类读文件命令但它管不了一个 Python 脚本自己open()文件那种 OS 级别的封堵得靠 sandbox后面的篇章再讲。记住//才是绝对路径、/是项目根就行。就这一个斜杠的差别能让你的 deny 规则彻底落空。六、六种权限模式和保护路径这道兜底第 2 篇入门时讲过常用的四档default、acceptEdits、bypassPermissions、plan。这篇补全到官方的六种模式不用问就能做的适合default只读起步、敏感工作acceptEdits读 改文件 常见文件系统命令mkdir/touch/mv/cp/rm/sed仅限工作目录内且保护路径与rm -rf /、rm -rf ~仍会拦下边改边审plan只读动手前先探索、出方案auto几乎全部但有后台分类器做安全审查长任务、减少打断dontAsk仅预先 allow 的其余自动拒不弹CI / 锁定环境bypassPermissions全部仅隔离容器 / VM有两点容易被忽略。一是ShiftTab默认只在 default、acceptEdits、plan 这三档之间循环。auto、dontAsk、bypassPermissions 不在默认循环里auto 要账号满足条件才会出现bypass 得用--dangerously-skip-permissions或者--permission-mode bypassPermissions启动之后才进循环dontAsk 干脆只能靠启动参数指定。其中 auto 这个新模式值得了解一下它放行大部分操作但后台有个独立的分类器模型在审查挡住下载并执行代码、把敏感数据发往外部、强推 main这类危险动作。不过官方也明说了它是研究预览能少打断你几次但不等于安全别拿它当 review 的替代品。二是最实用的那道兜底保护路径。除了 bypassPermissions对一批关键路径的写入永远不会被自动批准连 allow 规则都没法预先放行安全检查跑在 allow 之前。这批路径包括.git、.claude、.vscode、各种 shell 配置文件.zshrc、.bashrc、.envrc还有gradle-wrapper.properties、maven-wrapper.properties。还记得第 2 篇那张实拍吗Claude 核对天气 App 时发现仓库没有 gradle-wrapper。而gradle-wrapper.properties恰好就是保护文件之一。就算你开了 acceptEdits它也不会擅自帮你改 wrapper 配置而是停下来问你。这套兜底正是放权但不至于失控的最后一道防线。所以日常用ShiftTab那三档就够了。放权放得再大.git、shell 配置、wrapper 这些保护路径也不会被自动改除非你进了沙箱专用的 bypass。七、三段可以直接抄的配置机制讲了这么多落到~/.claude/settings.json或者项目的.claude/settings.json里给你三段能直接抄的。① 日常提速放行常用守住危险{ permissions: { allow: [ Bash(npm run *), Bash(git commit *), Bash(git push origin *) ], deny: [ Bash(git push * main), Read(.env), Read(.env.*), Read(./secrets/**) ] } }npm run、git commit免确认推 main 被 deny 兜底拦下.env 和 secrets 目录对 Claude 直接隐身。记住第二节的教训别想着在 allow 里加一条git push * main来开例外deny 先查例外开不出来。② 不熟的项目默认先出方案{ permissions: { defaultMode: plan } }③ 用域名白名单替代 curl更靠谱{ permissions: { deny: [Bash(curl *), Bash(wget *)], allow: [WebFetch(domain:docs.qq.com), WebFetch(domain:*.mycompany.com)] } }为什么不直接写Bash(curl https://xxx *)去限制 curl 的目标因为那样很脆。官方列了一串绕过的法子把-X GET放到 URL 前面、换成 https、用短链接重定向、用变量URL... curl $URL甚至多打一个空格都能让你的规则失效。靠谱的做法就是上面这样deny 掉命令行的联网工具改用WebFetch(domain:...)走域名白名单。allow 放高频的、deny 守底线、敏感文件让它看不见、联网走域名白名单。这套组合下来比全程点确认省心比全自动安全。八、两条边界必须记住这篇讲的是放权。越是讲放权越得把边界说清楚不然就是帮倒忙。第一权限规则是 Claude Code 在执行不是模型在执行。你在 CLAUDE.md 或者提示词里写不要碰数据库那只影响它想不想去碰真正决定允不允许它碰的是这篇讲的权限规则、权限模式或者 PreToolUse hook。想要硬边界就写 deny 规则别指望靠一句叮嘱。第二靠约束命令参数的规则天生脆弱别拿它当安全墙。第七节那个 curl 的例子就是典型。真要封网络或者文件访问靠域名白名单、PreToolUse hook或者 OS 级的 sandbox这些是后面进阶篇第 7 篇的内容。权限规则挡得住Claude 主动想去做的事挡不住一个它跑起来的脚本在底层自己干的事。说到底权限是你说了算的硬规则不是叮嘱它的软约束。要硬边界用 deny 和 hook别拿脆弱的参数匹配凑数。九、放权的本质是把判断写成规则回到开头那个纠结全程确认太烦全自动又不放心。这篇给的其实是同一把钥匙的两面。动手前用计划模式先看清它要干什么动手中用权限规则把你能干、你先问、你绝对不能固定下来。这两件事都到位你既不用一步步盯着点允许也不用把方向盘整个交出去、还蒙着眼。再往深一层看这跟整个系列反复碰到的那条线是一致的Claude Code 用得好不好说到底是你能不能把对它的约束从脑子里的担心变成系统里的规则。CLAUDE.md 是把上下文写成规则权限配置是把放权写成规则。规则写准了手才放得安心。