配置篇:设置、权限管线与钩子 —— Claude Code 的基因、护栏与神经

Claude Code; Agent; 权限管理 2923 字 15 min read

前两篇我们建立了 Claude Code 的心智模型,也过了一遍每项功能怎么用。这一篇要往深处走一层:你到底是怎么”管住”这个 Agent 的?

当 Agent 能自主调用几十个工具——写文件、跑命令、改 Git——每一次调用都潜藏风险。一个不恰当的 rm -rf,一次意外的 npm publish,都可能造成不可逆的后果。Claude Code 用三套系统来解决这个问题:

  • 配置系统 决定 Agent 天生”是什么样”——它的基因;
  • 权限管线 决定 Agent “能不能”做某件事——它的护栏;
  • 钩子系统 决定 Agent 在做某件事”前后”发生什么——它的神经。

三者层层递进。理解它们的协作关系,你就掌握了驯服 Claude Code 的全部抓手。

一、配置系统

Claude Code 的行为不是由某一个配置文件决定的,而是由六层配置源逐层合并而成。就像生物的基因在受精卵时就写定、在发育中逐层表达一样,这些配置在 Agent 启动前就决定了它能做什么、以什么方式做。

优先级从低到高

pluginSettings → userSettings → projectSettings → localSettings → flagSettings → policySettings
配置源文件路径说明入 Git
pluginSettings插件目录插件带来的基础默认值N/A
userSettings~/.claude/settings.json你的个人全局设置N/A
projectSettings<项目>/.claude/settings.json团队共享,提交到 Git
localSettings<项目>/.claude/settings.local.json个人本地覆盖,不入 Git
flagSettingsCLI --settings 参数一次性覆盖N/A
policySettings平台相关的 managed-settings.json企业级锁定,最高优先级N/A

核心原则是后加载者覆盖前者,但”覆盖”并非全量替换,而是按类型走不同的合并策略:

  • 数组类型
    (而非替换)。比如 permissions.allow累积所有层的规则。
  • 对象类型
    ,嵌套属性逐层覆盖。
  • 标量类型
    。比如 model 字段,高优先级源直接顶掉低优先级源。

为什么数组要”拼接”而不是”替换”?

这是一个值得琢磨的设计决策。在权限系统里,每一条规则都是一道防线。如果高优先级源的数组会替换掉低优先级源的数组,就意味着高优先级源必须完整枚举所有需要的权限——任何遗漏都会变成安全漏洞。拼接策略让每一层只关心自己要”增加”什么,系统自动合并所有层的安全策略。

反模式警告: 不要指望用”上层设一个空数组”去清除下层的规则——空数组拼接后,下层规则依然存在。如果要撤销某条允许,应该用 permissions.deny 字段显式拒绝,而不是试图”抹掉”allow。

举个完整例子。假设小张的个人全局设置允许 Bash(npm *),团队的项目设置允许 Bash(npm test)Read(*),小张的本地设置又把 model 改成了 opus。合并后:model 用本地的 opus(标量覆盖),permissions.allow 是三层规则拼接去重后的并集。每一层只写自己关心的部分,互不删除,只是被”遮盖”。

策略层的特殊地位

policySettings(企业管理策略)不走深度合并,而是”首个非空源胜出”——按 远程 API > MDM > managed-settings.json > 注册表 的顺序,找到第一个非空的就用它,不再往下合并。

原因是

、经过审计的方案,不同来源之间不应该互相渗透。远程下发的策略已经包含完整安全规则,不该被本地某个文件里的部分配置”稀释”。这一层是普通用户改不动的,它是组织给 Agent 上的最外层锁。

二、权限管线

配置定义了规则,权限管线负责在每次工具调用时执行这些规则。它不是一个简单的”允许/拒绝”开关,而是一条四阶段的检查管线,任何一个阶段做出终局决定,后续阶段就短路不再执行。

打个比方

,而是分层设卡——大堂刷卡、会议室预约、机房指纹加双人授权。即使某一层被绕过,下一层仍能拦住。这就是”纵深防御”。

阶段一
(validateInput)

第一道关卡不是权限,而是数据合法性。工具输入先过 Zod Schema 校验,如果结构不合法(比如 Bash 工具缺了 command 字段),不会直接崩溃,而是优雅降级为 ask(请求用户确认)。

这体现了安全系统的一个重要原则:错误处理应该是”安全的”而非”正确的”。直接崩溃会中断整个会话;降级为用户确认,则把决定权交还给人。

阶段二
(优先级铁律)

这是管线的核心。系统按严格顺序检查三类规则:

deny  >  ask  >  allow

优先级铁律: deny 永远优先于 allow,无论来源。即使你在全局配置里 allow 了某个工具,只要项目级配置 deny 了它,它就用不了。“显式拒绝”的力量永远大于”显式允许”。

规则来源本身也有优先级,遵循”就近原则”:会话级 > 命令行参数 > 项目配置 > 全局用户配置——越具体、越近期的规则,越能覆盖越一般、越早期的规则。

阶段三
(checkPermissions)

每个工具可以实现自己的精细评估逻辑。比如 Bash 工具会解析命令里的子命令、检查路径安全、匹配前缀规则——Bash(npm *) 这种通配符规则正是在这一阶段生效的。

这一阶段返回四种结果:allow(放行)、deny(拒绝)、ask(要用户确认)、passthrough(没意见,交给后续决定)。passthrough 是个精巧的设计

”我没意见”,如果后续有 allow 规则匹配就被覆盖为放行;而 ask 表示”我认为需要确认”,不会被 allow 覆盖。一个字之差,行为不同。

阶段四
(用户确认)

当管线流转到 ask,就轮到人了。系统渲染确认界面,等你选”允许/拒绝/本次允许”。有意思的是,在你思考的同时,系统可能并行跑一个 AI 分类器检查——谁先做出决定谁生效,形成一种”竞赛”机制。

落到实处

这套机制翻译成日常操作,就是预授权(第二篇提到的 /permissions)。把已知安全的命令加进 allow 白名单,把危险命令(如 rm -rf)加进 deny 黑名单:

{
  "permissions": {
    "allow": ["Bash(ls:*)", "Bash(npm test)", "Read(*)"],
    "deny": ["Bash(rm -rf:*)"]
  }
}

不预授权,你就会陷入”每做一件事都要确认”的困境,无法脱手长任务。预授权是把权限管线的规则提前写好,让 Agent 在安全边界内自由跑。

三、钩子系统

如果说权限管线决定 Agent 能否做某事,那么钩子系统决定了在做某事前后发生什么。它是 Claude Code 的”神经反射弧”——不修改核心逻辑,却能在关键节点插入条件反射式的行为。

钩子的本质是观察者模式 + 责任链模式

”信号”,多个钩子监听同一信号、按优先级依次处理,任何一个钩子都能选择”阻断传播”。

五种钩子类型

类型执行引擎延迟典型场景
CommandShell 命令毫秒级脚本检查、格式化、审计日志
PromptLLM 推理秒级需要”智能判断”的内容审核
AgentLLM 多步秒~分钟测试验证等复杂流程
HTTPHTTP 请求网络依赖CI/CD 集成
FunctionTS 回调毫秒级运行时拦截(不可持久化)

最常用的是 Command 钩子——就是在某个事件触发时跑一段 shell。比如”Agent 干完活后发系统通知""每次写文件前自动格式化""拦截 rm -rf”。配置长这样:

{
  "hooks": {
    "PreToolUse": [{
      "matcher": "Bash",
      "hooks": [{
        "type": "command",
        "command": "python3 scripts/validate_command.py",
        "timeout": 5000
      }]
    }]
  }
}

关键心法
”提交时”卡,而不是”写入时”卡

来自一线企业实践的经验(译自 sshh.io)

提交时阻断

设一个 PreToolUse 钩子,包裹 git commit 命令。它检查一个标志文件,这个文件只有在所有测试通过时才由测试脚本创建。文件不存在,钩子就阻止提交,逼着 Agent 进入”测试并修复”的循环,直到构建通过。

反过来,要刻意避免”写入时阻断”(在 Edit/Write 操作上拦截)。在 Agent 执行计划的中途打断它,会让它困惑甚至”沮丧”,打乱它的整体思路。更有效的做法是:让它完成计划,然后在提交这个关口检查最终的完整结果。

一句话总结

确定性的”必须做”,和 CLAUDE.md 里**建议性的”应该做”**互为补充。规则性的、每次都要发生的事,交给钩子;引导性的、需要理解的事,写进 CLAUDE.md

三者如何协作

把三套系统串起来看,一次工具调用的完整旅程是这样的:

flowchart TD
    config["配置系统<br/>六层合并出最终规则"] --> req["Agent 发起工具调用"]
    req --> hook_pre["PreToolUse 钩子<br/>(神经:执行前拦截)"]
    hook_pre --> perm["权限管线<br/>(护栏:四阶段检查)"]
    perm -->|deny| blocked["拒绝执行"]
    perm -->|allow| exec["执行工具"]
    exec --> hook_post["PostToolUse 钩子<br/>(神经:执行后处理)"]
    hook_post --> done["完成"]

    classDef cfg fill:#e8f5e9,stroke:#4caf50,stroke-width:2px,color:#1b5e20
    classDef guard fill:#ffebee,stroke:#f44336,stroke-width:2px,color:#b71c1c
    classDef nerve fill:#fff3e0,stroke:#ff9800,stroke-width:2px,color:#e65100
    class config cfg
    class perm,blocked guard
    class hook_pre,hook_post nerve
  • 配置是静态的基因,在启动时写定规则;
  • 权限管线是动态的护栏,在每次调用时执行规则;
  • 钩子是可编程的神经,在规则执行的前后插入你的自定义逻辑。

理解这个协作链条,你就能回答一个关键问题:当 Agent 做了不该做的事,我应该改哪里? 如果是”某类操作永远不许”,改 permissions.deny;如果是”某类操作要先满足某个条件”,写钩子;如果是”某类操作的默认行为要变”,调配置。对症下药,而不是每次都靠对话临时纠正。

小结

  • 配置系统六层合并,数组拼接、标量覆盖,deny 优先,企业策略层锁定;
  • 权限管线四阶段纵深防御,预授权是它的日常落地方式;
  • 钩子系统五种类型,核心心法是”提交时卡、写入时放”,与 CLAUDE.md 分工(“必须做” vs “应该做”)。

下一篇进入扩展篇

,如何用 Skills 技能系统给 Claude Code 装上”外挂”,以及插件架构如何让能力可分享、可复用。


本文主要参考:

  • 《御舆
    Agent Harness》(lintsinghua/claude-code-book,CC BY-NC-SA 4.0) 第 4 章”权限管线”、第 5 章”设置与配置”、第 8 章”钩子系统”
  • How I Use Every Claude Code Feature(钩子的”提交时阻断”实践)
  • Claude Code 官方文档 settings / hooks-guide

本文为学习整合,配置示例经简化,实际字段以官方文档为准。