想象一下,您在 Manual 模式下工作了一小时。智能体阅读项目、提出修改、请求运行测试,然后又改一轮、再跑一次测试。到第二十次询问时,您几乎没看就点了 "Yes"。而在这些询问之间,一处对 legacy/ 文件夹的修改就这样混了过去,这个文件夹早就没人碰,也不该有人碰。

本文要讲的是,怎样只在确实需要您决定的地方保留确认。Claude Code 提供了权限规则:设置文件中的三个列表,分别说明哪些操作可直接执行、哪些需要询问,以及哪些绝不能执行。我们来看看它们为何有用、该怎么读,以及如何整理您很可能已经有的规则文件。

一个设置管所有操作

权限模式就像控制整个会话的一只旋钮。您可以往任一方向调,但只能整体调整。

在严格的 Manual 模式下,智能体每次修改、几乎每条命令前都会询问。只要您认真阅读,询问就能起到保护作用。可一旦问题太多,人们就不再逐条看,保护也只剩形式。

在宽松的 acceptEdits 模式下,修改可以直接通过,rm、mv 这类文件操作命令也一样。处理自己的代码时,这很方便。但模式不会区分文件夹:修改 legacy/ 和项目其他位置一样,也会悄无声息地通过。

您可以在 CLAUDE.md 中写上 "不要改 legacy/"。智能体会尽量遵守,但这只是对模型的请求,不是强制限制。Claude Code 会在操作执行前检查权限规则;CLAUDE.md 或请求中的文字不会改变这些规则。

问题在于,不同操作出错的代价不同,权限模式却一视同仁。运行测试可以直接放行;修改他人负责的模块,您可能希望先看一眼;而冻结目录则根本不该让智能体进入。三种操作需要三种处理方式,单靠模式做不到。

三个列表

权限规则正好能给出这三种处理方式:

  • allow - 直接执行,不再询问。减少干扰:常见且安全的操作不会反复打断您;
  • ask - 每次都先询问。需要您决定的地方会保留确认,而且任何模式下都有效,即使模式很宽松也一样;
  • deny - 禁止执行。智能体会收到拒绝,您甚至看不到确认提示。任何模式下都有效。

规则写在设置文件中,例如项目的 .claude/settings.json。前面提到的项目可以这样配置:

{
  "permissions": {
    "allow": [
      "Bash(npm test)"
    ],
    "ask": [
      "Edit(src/partner/**)"
    ],
    "deny": [
      "Edit(legacy/**)",
      "Bash(git push *)"
    ]
  }
}

这里的 src/partner/ 是由他人维护、需要谨慎修改的模块;legacy/ 是已冻结的项目区域;npm test 则是智能体在一个任务中要运行十来次的检查。两个星号表示"文件夹内任意层级的所有内容"。

接下来看看会话中的变化。同样五种操作,在三种配置下分别会怎样:

操作Manual,无规则acceptEdits,无规则acceptEdits,有规则
修改其他 src/ 文件询问不询问不询问
修改 src/partner/ 中的文件询问不询问询问
修改 legacy/ 中的文件询问不询问拒绝
npm test询问询问不询问
git push询问询问拒绝

请看最后一列。会话在宽松模式下运行:智能体可以连续修改自己的代码,测试也会自动运行。只剩一个询问,正好落在您确实想检查修改的位置。另外两种不允许的操作,不再取决于您晚上有多疲惫。

规则的作用就在这里。重点不是给智能体更多权限,而是按出错代价分配权限。这样您可以使用更宽松的模式,同时守住重要边界。

同一操作可能匹配多条规则,因此 Claude Code 会按固定顺序检查列表:

规则检查顺序。智能体的操作先与 deny 列表匹配:匹配则拒绝。若未匹配,再检查 ask 列表:匹配则在任何模式下都要询问。若仍未匹配,再检查 allow 列表:匹配则直接执行。如果都不匹配,由模式决定:Manual 下智能体会询问,acceptEdits 下修改会直接通过。智能体的操作命令、修改、读取匹配 deny?匹配 ask?匹配 allow?拒绝操作不会执行询问任何模式下无需询问立即执行都未匹配 - 由模式决定Manual 下会询问,acceptEdits 下修改直接通过是是是否否否
从上往下检查,第一个匹配项决定结果

图示要从上往下看,智能体的每项操作都会重新走一遍检查。一旦匹配到规则,检查就结束,更具体的规则也不会因此获得优先权。如果 deny 中有 Bash(git push *),那么 allow 中的规则也不能放行 push。禁止项不会被意外覆盖,这正是我们想要的。

底部的矩形值得单独记住。操作没有匹配任何列表,并不等于"允许"。最终由模式决定:Manual 下智能体会询问,acceptEdits 下修改会直接通过。也就是说,规则不会取代模式,而是在您指定的范围内细化模式。

如何理解一条规则

一条看似正确、实际写错的规则,比没有规则更糟。没有规则时,您知道边界还没设好;规则不准确时,您以为边界已经生效,结果保护的却不是您想保护的内容。

所有规则格式相同:工具名称,后面括号里写具体范围。没有括号时,规则会匹配该工具的所有调用。因此,把 Bash 放进 deny,就会彻底禁用智能体的终端。

文件

文件操作涉及两个工具:Edit 用于所有修改,Read 用于读取。路径使用类似 .gitignore 的模式:一个星号匹配当前文件夹中的任意名称,两个星号则匹配任意层级。开头没有斜杠的路径以会话启动目录为基准,因此请从项目根目录启动 Claude Code。

常见错误是写成 Write(legacy/**)。智能体确实有名为 Write 的工具,但 Claude Code 只用 Edit 和 Read 来检查文件规则。它会接受 Write 规则,在启动时发出警告,之后却不会再检查它。Edit 才能覆盖所有内置文件修改方式,包括创建新文件。

还有一个实用之处:例如在 deny 中禁止读取 Read(./.env),也会一并禁止智能体修改这个文件。

命令

Bash(...) 规则会比较命令文本。星号可以替代任意文本,包括空格。以下三个细节会决定规则是否匹配:

  • 没有星号时,规则只匹配完全相同的一条命令;
  • 星号通常放在子命令之后。Bash(git log *) 只允许带任意标志的 git log;而 Bash(git *) 会允许任何 git 命令,包括 push;
  • 星号前的空格也属于规则的一部分:Bash(ls *) 不会匹配 lsof,但 Bash(ls*) 会匹配。
命令规则如何与文本匹配。规则 Bash(git log *):git log 部分必须完全一致,星号匹配 --oneline main,因此规则只允许带任意标志的 git log。规则 Bash(git *):只有 git 部分需要完全一致,星号匹配 push origin main,因此它会允许任何 git 命令,包括 push。Bash(git log *)Bash(git *)仅 git log,可带任意标志任何 git 命令,包括 pushgit log--oneline maingitpush origin main星号之前:必须完全一致星号位置:匹配任意文本
星号之前的文本决定规则范围

蓝色部分必须与命令文本完全一致,橙色部分则由星号整体匹配,不管具体是什么内容。因此,规则范围取决于星号之前写了什么。第一条规则星号前是 git log,除了查看历史,它什么都不会放行。第二条星号前只有 git,星号后面可能是 push、删除分支或重置更改。阅读别人的规则时,可以先问一句:星号前面是什么?

规则匹配不匹配
Bash(npm test)npm testnpm test --watch
Bash(npm run *)npm run build、npm run lint --fixnpm install
Bash(git log *)git log、git log --onelinegit push
Bash(git *)任何 git 命令,包括 git push-
Edit(src/partner/**)修改 src/partner/ 中的任意文件修改 src/app.js
Read(./.env)读取项目根目录中的 .env读取 .env.example

对于 a && b 这样的复合命令,Claude Code 会拆分后逐段检查。只有每一段都符合对应规则时,allow 才会放行;只要有一段匹配,ask 和 deny 就会生效。因此,允许 npm test 并不会顺带放行 npm test && git push。

其他工具也可以设置规则,例如 mcp__github__* 匹配某个 MCP 服务器的所有工具,WebFetch(domain:example.com) 则对应从某个网站加载页面。它们的逻辑相同,这里不再逐一展开。

规则应该放在哪个文件

规则放错文件,问题往往悄悄发生。把禁止项写进个人文件,团队成员看不到,他们的智能体仍会照常进入 legacy/。把只适合自己的许可写进共享文件,所有下载项目的人都会得到这条规则。

文件规则适用范围是否纳入 Git
~/.claude/settings.json您在这台电脑上所有项目中的个人规则否,文件位于主目录
.claude/settings.json所有参与此项目的人的共享规则是,会提交到仓库
.claude/settings.local.json您在此项目中的个人规则否

除了这三种文件,组织也可能统一下发设置文件,其规则无法覆盖。不过教学项目通常不会有这类文件。

不同文件中的列表不会互相冲突,而是合并生效:您个人文件中的 allow 会与共享文件中的 allow 合并。所有文件遵循相同的检查顺序,而且任何文件中的 deny 都优先于其他文件中的 allow。因此,像 legacy/、.env 或 push 这样的项目边界,应放进共享文件,让规则随仓库一起交付。符合个人工作习惯的便利许可,则放进本地文件。

共享文件还有一道保护。只有您确认信任该文件夹后,其中的 allow 规则才会生效:首次在项目中启动 Claude Code 时,它会询问您,并展示该文件将允许哪些操作。ask 和 deny 会立即生效,因为它们只会增加限制。即使陌生仓库允许使用整个终端,在您同意之前,这条规则也不会起任何作用。

可以用两个命令确认当前实际生效的规则。/permissions 会列出所有规则及其来源文件,也可以在这里添加或删除规则。/status 的 Setting sources 一行则会显示已加载的文件。Claude Code 无需重启就能读取文件修改。设置文件必须是严格的 JSON:添加 // 注释或在最后一项后多写逗号都会导致解析失败,Claude Code 启动时会提醒您。

自动写入的文件

智能体询问某条命令时,如果您选择 "Yes, and don't ask again",Claude Code 会把对应的 allow 规则写入 .claude/settings.local.json。如果文件还不存在,它会新建文件,并确保文件不会被提交。文件修改的处理方式不同:这类同意只在当前会话结束前有效,不会写入文件。

拆解一个真实的 settings.local.json

您很可能已经有这个文件,而且并非亲手编写。每次选择"不再询问"都会往里面添加一条规则。几周后,它可能允许智能体做的事远超您的预期。下面是某个 Python 项目在几周使用中积累下来的真实配置:

{
  "permissions": {
    "allow": [
      "Bash(python3 *)",
      "Bash(PYTHONIOENCODING=utf-8 python3 *)",
      "Bash(git status *)",
      "Bash(git log *)",
      "Bash(git diff *)",
      "Bash(git show *)",
      "Bash(git branch *)",
      "Bash(git add *)",
      "Bash(git commit *)",
      "Bash(git fetch *)"
    ]
  }
}

继续读解析之前,不妨先自己判断:哪些规则应该保留、收窄或删除?

Bash(python3 *)
这是文件中范围最宽的一条。它允许运行任意脚本,也允许通过 python3 -c 执行任意代码。Python 几乎什么都能做:删除文件、访问网络、启动其他程序。实际上,这相当于允许任何操作直接执行而不询问。建议把规则收窄到它原本要允许的那个脚本,例如 Bash(python3 scripts/build.py *)。

Bash(PYTHONIOENCODING=utf-8 python3 *)
这是前面加了环境变量的同一条命令。只有当该变量属于一小组预先认定安全的变量时,allow 规则才会放行这种写法。如果变量不在列表中,Bash(python3 *) 就无法匹配,智能体会另行询问,而您点击按钮后又会保存一条规则。这条规则很可能就是这样产生的。收窄方法相同,限定到具体脚本即可。

git status、git log、git diff、git show
这些命令通常只读取信息,而 Claude Code 无需规则或询问就能执行读取操作。保留它们没什么坏处,但几乎没有作用。删掉之后,文件会更短,也更容易读。

Bash(git branch *)
看起来没什么风险,只是查看分支列表。但规则比较的是命令文本,而 git branch -D feature 也以 git branch 开头。这样一来,删除分支也会直接通过。最好删掉这条规则。如果查看分支时总被询问,再精确地允许 Bash(git branch),不要加星号。

git add 和 git fetch
前者会把更改加入待提交内容,后者从服务器下载最新历史记录,但不会改动工作文件。这两项操作都容易撤销,可以保留。

Bash(git commit *)
智能体会在您查看 diff 之前就直接提交。这里恰恰应该先询问:提交意味着您认可这次更改。建议把规则移到 ask。单纯删除还不够。在 Manual 模式下智能体本来就会询问,但 ask 能在任何模式下保留确认,包括最宽松的模式。

文件中没有的规则
git push。这样很好,但前提是之前没人对 push 点击过"不再询问"。明确添加禁止规则会更可靠。

整理后会得到这样的配置:

{
  "permissions": {
    "allow": [
      "Bash(python3 scripts/build.py *)",
      "Bash(PYTHONIOENCODING=utf-8 python3 scripts/build.py *)",
      "Bash(git add *)",
      "Bash(git fetch *)"
    ],
    "ask": [
      "Bash(git commit *)"
    ],
    "deny": [
      "Bash(git push *)"
    ]
  }
}

十条规则最后只保留六条,每条都有明确作用。工作中的询问几乎没有增加:构建会自动运行,智能体只在提交前询问一次。如果 push 禁令适用于整个团队,最好按上一节的建议,将它移入共享的 .claude/settings.json。

允许整个程序运行

Bash(python3 *)、Bash(node *)、Bash(npm *) 或 Bash(npx *) 这样的规则,看起来只是允许使用一种工具。实际上,它允许执行该工具能够运行的任何代码。请只允许具体脚本或具体命令,不要放行整个程序。

规则无法拦截什么

智能体收到拒绝后,会把它当作普通的工具响应,然后继续工作。有时它会尝试用另一种方式完成同一件事,因此您需要清楚规则具体检查什么。

Bash(...) 规则比较的是命令文本,而不是即将启动的程序。因此,禁止规则可以拦截常见写法,却未必能覆盖所有写法:

  • deny 中的 Bash(git push *) 拦不住 git -C . push。命令相同,写法不同;
  • Bash(rm *) 拦不住 /bin/rm 或 bash -c 'rm ...';
  • 禁止 Read(...),也拦不住自行打开文件的 Python 脚本。

这并不意味着规则没有用。智能体几乎总会用常见形式写命令,deny 正好能拦截这种写法。不过规则并不是坚不可摧的屏障(Claude Code 文档也明确说明了这一点)。为了避免智能体尝试绕过规则,请在 CLAUDE.md 中用文字说明:"legacy/ 已冻结;如果任务需要修改其中的内容,请停下来询问"。规则负责拦截,CLAUDE.md 负责解释,两者配合起来比单独使用效果更好。

如果必须可靠地守住边界,还可以使用其他机制:在每条命令运行前进行检查的 hook,以及在系统层面限制访问的 sandbox。这些是另外的话题。

日常使用规则

归纳起来,可以记住下面几条:

  • 把任何情况下都不允许的操作放进 deny:例如冻结目录、.env 和 push;
  • ask 只用于需要您判断的边界情况。什么都设为 ask,又会带回开头那种对询问的麻木;
  • allow 应该范围窄,只放行重复出现的操作:具体检查、具体脚本;
  • 项目边界放进共享的 .claude/settings.json,个人便利设置放进 .claude/settings.local.json;
  • 时不时打开 /permissions,清理点击"不再询问"后积累的规则;
  • 添加新规则后做一次验证:请智能体尝试执行被禁止的操作,确认它确实收到拒绝。
也可以让智能体起草规则

用文字向智能体说明项目中的区域:哪些可以自由修改,哪些要先询问,哪些不能进入。请它拟一段 permissions 配置,保存前用上面拆解时的那些问题检查一遍 JSON。规则范围是否过宽?它会写入正确的文件吗?

模式决定整体工作节奏,规则则指出哪些地方应该放慢脚步。两者配合,才能做到单靠模式无法实现的效果:在出错代价低的地方快速推进,在代价高的地方先询问或直接拒绝。