配置篇:设置、权限管线与钩子 —— Claude Code 的基因、护栏与神经
前两篇我们建立了 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 | 否 |
| flagSettings | CLI --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 是个精巧的设计
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 的”神经反射弧”——不修改核心逻辑,却能在关键节点插入条件反射式的行为。
钩子的本质是观察者模式 + 责任链模式
”信号”,多个钩子监听同一信号、按优先级依次处理,任何一个钩子都能选择”阻断传播”。五种钩子类型
| 类型 | 执行引擎 | 延迟 | 典型场景 |
|---|---|---|---|
| Command | Shell 命令 | 毫秒级 | 脚本检查、格式化、审计日志 |
| Prompt | LLM 推理 | 秒级 | 需要”智能判断”的内容审核 |
| Agent | LLM 多步 | 秒~分钟 | 测试验证等复杂流程 |
| HTTP | HTTP 请求 | 网络依赖 | CI/CD 集成 |
| Function | TS 回调 | 毫秒级 | 运行时拦截(不可持久化) |
最常用的是 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
本文为学习整合,配置示例经简化,实际字段以官方文档为准。