Claude Code 权限提示陷入循环

明明给过 allow,每次 Bash、Edit、Read 都还在问——settings.json 范围错了、allowlist pattern 写得太窄,或者有 hook 在重复问。

你刚给一个 Bash(npm test) 点过 allow,两分钟后 Claude 再跑这条命令同样的提示又跳出来。你往 allowlist 加了一条,下一个 Bash(npm run build) 还是在问。每次 Edit、每次 Read、每次 shell 命令都要点一下——你以为的”agent”实际上是个要点一百次确认的钥匙保管员。原因基本都是这三种:settings.json 范围搞错、allowlist pattern 和实际命令对不上、或者有个 hook 设计上就要重复确认。把范围修对、pattern 精准放宽,一个 session 100 次提示能降到 5 次。

常见原因

1. allowlist 比实际命令更具体

settings.json 里写的是 "Bash(npm test)",但 Claude 跑的是 Bash(npm test --watch) 或者 Bash(cd packages/web && npm test)。匹配是工具调用层面的精确字符串,长一点的形态命中不了 allow 规则。

如何判断:弹提示时看清完整工具调用文本,和你 allowlist 里的写法对比。哪怕只是末尾多了个 flag——pattern 就是太窄。

2. allowlist 放错 settings 范围

.claude/settings.local.json(每用户每项目)、.claude/settings.json(仓库共享)、~/.claude/settings.json(全局用户)是三个独立文件。规则放错文件就被忽略。

如何判断:看清你的规则在哪个范围。cat .claude/settings.jsoncat .claude/settings.local.jsoncat ~/.claude/settings.json 三个都看一遍——必须放在 Claude 真的在这个项目里读的那个范围里。

3. 有 pre-tool hook 强制重新确认

PreToolUse 下的 hook 能编程式要求确认。如果你有一个对每个 Bash 都问的 hook、再厉害的 allowlist 也压不住。

如何判断:打开 ~/.claude/settings.json 和项目的 .claude/settings.json。找 hooks.PreToolUse 里 matcher 是 Bash / Edit / Read 的条目。有的话就盖过 allowlist。

4. 规则写在 deny 不在 allow

常见笔误是把规则放到 "deny" 而不是 "allow"。扫一眼 JSON 看着对、实际效果反了。

如何判断:打开 settings.json 顺着 key 看:permissions.allow 是放行规则、permissions.deny 是阻断。放错位置要么没效果要么主动 block。

5. settings 中途改了、没重新加载

有些版本启动时缓存权限集——中途改了 settings.json、正在跑的 Claude 用的还是老版本。规则要是限定到了项目、当前工作目录还可能对不上。

如何判断:改完 settings.json 同样的提示还跳。重启 Claude Code;规则限定到项目的话再确认 pwd 是不是规则所属的项目。

动手前先确认

  • 确定到底哪些工具调用在循环(什么命令的 Bash?哪个文件的 Edit?)。要拿到精确字符串。
  • 看清你在编辑的是哪一个 settings 文件——三层不会透明合并。
  • 改之前先备份 settings 文件,JSON 写坏了 Claude Code 启动不了。

需要收集的信息

  • 最近一次权限弹窗的完整工具调用字符串。
  • ~/.claude/settings.json、项目 .claude/settings.json.claude/settings.local.json 三个文件的内容。
  • 有没有自定义 hook 在 hooks.PreToolUse 里。
  • Claude Code 版本(claude --version 或应用内构建号)。
  • session 的工作目录(pwd)。
  • 是否点过 “Always allow”——可能是误点了 “Just this time”。

最短修复路径

Step 1:抓住完整工具调用文本

下次弹提示别急着点,先看清完整文本。注意工具名和参数字符串。形如:

Bash(cd packages/web && pnpm run build)
Edit(/Users/you/proj/src/api/auth.ts)
Read(/Users/you/proj/CLAUDE.md)

工具名后面的整段就是你 allowlist pattern 必须匹配的东西。

Step 2:选对 settings 范围

按这张表选要改的文件:

规则适用范围改哪个文件
跨所有项目的个人默认~/.claude/settings.json
团队通过 git 共享仓库根的 .claude/settings.json
这个项目里个人用、不进 git.claude/settings.local.json

npm test 这种项目特定但个人用的命令、放 settings.local.json 不污染同事的环境。

Step 3:写一条覆盖同族调用的 pattern

用 shell glob 风格覆盖同一意图的多种变体:

{
  "permissions": {
    "allow": [
      "Bash(npm test*)",
      "Bash(pnpm run *)",
      "Bash(git status)",
      "Bash(git log*)",
      "Bash(cd * && npm *)",
      "Edit(/Users/you/proj/src/**)",
      "Read(/Users/you/proj/**)"
    ]
  }
}

* 匹配任意——用它吸收末尾 flag 和参数。要有意识——宽规则方便、但也会放过你没打算放过的命令。

Step 4:验证规则真的被读到

保存 settings 后重启 Claude Code,再故意触发循环命令:

让 Claude:"跑 npm test"
预期:不弹提示
实际:还在弹 → 规则没匹配上

还在弹就检查:

  • JSON 合法(python -m json.tool < settings.json 跑一遍)。
  • 文件在 Claude 真的读的范围(claude --debug 打印加载的 settings)。
  • 没有 deny 规则盖过你的 allow(deny 优先于 allow)。

Step 5:检查和禁用有问题的 hook

有 hook 在强制确认就找出来:grep -A 5 "PreToolUse" ~/.claude/settings.json .claude/settings.json。matcher 是 Bash 又跑交互确认的 hook 会让每个 Bash 重新提示、allowlist 也压不住。不需要就删;需要的话把 matcher 收窄、只对真正破坏性的命令生效。

Step 6:信任的项目里宽 allow

自己的项目里、信任 Claude 判断的话,可以激进 allowlist:

{
  "permissions": {
    "allow": [
      "Bash(*)",
      "Edit(*)",
      "Read(*)",
      "Grep(*)",
      "Write(*)"
    ],
    "deny": [
      "Bash(rm -rf *)",
      "Bash(* >/dev/null *)",
      "Bash(git push --force*)",
      "Bash(curl * | bash*)"
    ]
  }
}

“全放行 + 危险点 deny”让 agent 流畅运行、没有点确认的疲劳。

怎么确认已经修好

  • 跑一个 10 分钟探索性 session,数权限提示数。修完应该 <5 次(理想 0-2 次)。
  • 把原本循环的每条命令各手动触发一次,确认不再弹提示。
  • 跑一条明显破坏性的命令(rm -rf /tmp/test-fixture)确认会弹——deny 规则在生效。
  • 重启 Claude Code 后同样的放行命令还在静默跑——settings 持久化了。

长期预防

  • 每个项目定 allowlist 策略:信任的项目宽 allow + 定点 deny;安全敏感的项目只窄 allow。
  • 破坏性 deny 放 ~/.claude/settings.json 全局生效。
  • 个人机器的规则放 .claude/settings.local.json、别把开发者特定的模式漏进团队 settings。
  • 团队工作流加新工具时把 allowlist 条目加到共享的 .claude/settings.json、同事不用每人重做一遍。
  • 每次改完 settings.json 用 python -m json.tool 校验——一个逗号缺失某些版本会静默整个文件失效。
  • 每季度审查 hook、把已经不值得它造成摩擦的删掉。

常见坑

  • pattern 用 ? 或者正则语法、以为能用——Claude Code 是简单 glob 只有 *、不是完整正则。
  • 写了 Bash(*) 但一条 deny 都没——agent 早晚会在某个瞬间跑出风险操作。
  • 编辑了错的范围文件、然后断言”settings 不生效”——是生效的、你编辑错了。
  • 改完全局 settings 没重启 Claude Code——旧 session 还在用旧 allowlist。
  • 反复点 “Just this time”、然后惊讶提示一直回来。“Just this time” 设计上就不持久化。

FAQ

Q:“Always allow”、“This session”、“Just this time” 有啥区别? A:Always = 写进 settings.json;session = 退出前都记住;once = 下次还问。这里的误点是循环最常见的来源。

Q:allowlist pattern 能用完整正则吗? A:不能、只能用 * 的简单 glob。需要类似正则的能力就拆成多条。

Q:我的 deny 规则为啥没拦住该拦的命令? A:deny 匹配也得用对 pattern 形态。Bash(rm -rf /) 这种 deny 不匹配 Bash(rm -rf /tmp/x)——同族用 Bash(rm -rf *)

Q:MCP 服务端的工具走同一套权限系统吗? A:走——MCP 工具调用名字形如 mcp__server__tool、可以用一样的方式 allowlist。

相关阅读

标签: #Claude Code #agent #排查