我最早接触 Claude Code 的时候,它的定位很简单:一个能跑在终端里的 AI 编程助手。当时我拿它当高级版补全工具用,丢进去一个 issue 描述,它帮我改文件、跑测试、提 commit。真正让我改观的是我开始翻官方文档里写 customization 的部分——原来这个工具允许你改的不只是 prompt,而是它完整的运行机制。Claude Code 的 Mods 能力,正在把它从"一个 AI 工具"变成"一个可以被你重新设计行为模式的 AI 工具"。这篇文章我围绕自己实际改造它的过程,讲清楚运行机制到底指什么、能改哪些位置、怎么改才不会翻车,给同样想深入玩转 AI 编程工具的人一套可落地的思路。
1. 先搞清楚 Claude Code 的"运行机制"到底指什么
1.1 CLI 工具的完整运转链路
Claude Code 本质上是一个 Node.js 写的命令行程序。它的运行链路并不神秘:你在终端输入自然语言指令,程序把指令连同项目上下文一起打包成请求,送到模型,模型返回工具调用序列(读文件、写文件、执行命令、搜索等),程序实际执行这些调用,再把结果回填给模型,循环往复直到任务结束或者达到停止条件。
以前我用 AI 编程工具的时候,能动的只有"输入侧"——换个说法、换个 prompt,期待输出好一点。但 Claude Code 把"输入侧"之外的东西全部暴露给了使用者。你可以干预上下文怎么拼、任务开始前要做哪些检查、工具执行前允不允许、工具执行完要不要追加验证、最后输出用什么样的风格。这些位置组合起来,就是一个可以自定义的"运行机制"。
这就像玩同一台游戏机,以前你只能选择键位和画质,现在你能改物理引擎、改掉落率、改 NPC 的行为逻辑。Mods 这个词放在这里非常贴切:它指的不是简单的插件安装,而是对工具本身行为逻辑的重新组装。
1.2 可改造的六个切入面
我自己的经验是,先把改造面盘清楚再动手,比直接抄别人的配置重要得多。Claude Code 的改造入口可以分成下面这六层:
| 改造层 | 主要载体 | 作用位置 | 典型用途 |
|---|---|---|---|
| 记忆层 | CLAUDE.md 文件 | 系统提示词拼接阶段 | 注入项目规范、架构说明、约束条件 |
| 行为层 | Hooks 钩子 | 工具调用前后、会话周期节点 | 自动检查、拦截危险命令、记录结果 |
| 交互层 | 自定义斜杠命令、输出风格 | 指令解析与输出格式化 | 固化高频操作流程、统一回答形态 |
| 能力层 | MCP 服务器 | 工具集合扩展 | 接入数据库、浏览器、内部服务 |
| 配置层 | settings.json、环境变量 | 全局参数与认证 | 模型路由、权限策略、代理开关 |
| 协作层 | git 管理的 .claude 目录 | 多人共用 | 团队级规范同步、新成员快速上手 |
这六层不是并列关系,而是嵌套关系。记忆层决定模型"知道什么",行为层决定它"能怎么动作",能力层决定它"手里有什么工具",交互层决定你"怎么叫它干活"。把它们组合起来,才是真正意义上的改运行机制,而不是在某一个文件里塞两行配置。
2. 记忆层改造:CLAUDE.md 与项目上下文的工程化
2.1 CLAUDE.md 的加载规则
Claude Code 的记忆机制核心是 CLAUDE.md。它遵循一套简单但容易误用的加载规则:
- 项目根目录的
CLAUDE.md默认加载,是项目的全局记忆。 - 任意子目录里也可以放
CLAUDE.md,当对话涉及该目录时按需加载,适合维护局部模块的专属约定。 - 用户主目录下的
~/.claude/CLAUDE.md是跨项目的个人偏好记忆。 - 对话中可以用
@路径显式导入任意文件,哪怕是不在当前目录树里的说明文档。
刚开始我理解的"记忆"就是往文件里写一堆项目介绍。后来发现一个问题:模型每次请求都要把这些内容拼进去,写太多不仅费 token,还会稀释真正的关键约束。记忆层改造的核心不在"写得多",而在"写得准"。
2.2 一套可复用的记忆模板
下面这个模板是我在多个项目里迭代出来的,它解决了"项目记忆到底该写什么"的问题:
# 项目名 ## 一句话定位 这是一个面向 xxx 的 xxx 服务,核心指标是 xxx。 ## 架构速览 - 前端:React + Vite,代码在 apps/web - 后端:Go + PostgreSQL,代码在 services/api - 关键链路:gateway -> auth -> api -> db ## 常用命令 - 启动开发环境:make dev - 跑全部测试:make test - 单测某个包:go test ./internal/xxx -run TestXxx ## 代码规范 - 所有对外接口必须带超时控制 - 数据库迁移文件禁止修改,只能新增 - 错误处理统一走 errors 包,不直接 fmt.Println ## 高频任务约束 - 修改 API 时必须同步更新 OpenAPI 文档 - 涉及支付相关改动,必须补测试且至少覆盖成功/失败两条路径 ## 禁止事项 - 不要用全局变量保存用户状态 - 不要引入未在 go.mod 中声明的依赖注意这些条目的写法:全部是祈使句,直接告诉模型"做什么、不做什么"。我见过很多人写"项目应该注意代码质量"这种废话,模型看了跟没看一样。CLAUDE.md 的每一条都应该是可执行、可验证的指令,而不是价值观宣传。
2.3 记忆层最容易踩的坑
第一个坑是记忆膨胀。我有个项目,CLAUDE.md 写到了三百多行,结果模型开始忽略前置约束,反而过分关注低优先级内容。后来我在一个长对话里明显感觉它"忘掉"了最初的约束,排查下来发现不是上下文窗口的问题,而是太长的记忆让模型的注意力分配变得混乱。我的经验是:项目级 CLAUDE.md 控制在 60 行以内,超过就拆到子目录或做成@导入的独立文档。
第二个坑是优先级冲突。~/.claude/CLAUDE.md、项目级 CLAUDE.md、子目录 CLAUDE.md 同时存在时,谁优先并没有一个绝对透明的规则。我遇到过一次全局记忆里写了"所有代码用 TypeScript",而某个子项目其实是 Python,导致模型反复尝试生成.ts文件。解决办法是:全局记忆只放通用习惯,不碰任何可能与具体项目冲突的技术选型。
第三个坑是把它当成普通文档写。CLAUDE.md 是要被模型逐字读取的,不是给人读的 README。我后来养成了一个习惯:每写一条,就问自己"如果我是模型,看到这句话能产生一个确定的行为改变吗?"不能就删掉。
3. 行为层改造:Hooks 子系统的实战解析
3.1 Hooks 触发的完整事件链
如果说 CLAUDE.md 是给模型"灌知识",Hooks 就是在运行机制里"插桩"。Claude Code 的 Hooks 定义了一套生命周期事件,每个事件发生时都可以执行外部命令,并让命令的输出反过来影响工具行为。
我实际用到的核心事件是这几个:
| 事件 | 触发时机 | 典型用途 |
|---|---|---|
| UserPromptSubmit | 用户提交指令后、发送给模型前 | 改写问题、注入约束、拒绝敏感请求 |
| PreToolUse | 模型请求执行某个工具前 | 安全检查、命令白名单、文件路径校验 |
| PostToolUse | 工具执行完成后 | 自动格式化、补跑测试、校验产物 |
| Stop | 一次完整任务结束时 | 生成总结、通知、上报结果 |
| SubagentStop | 子代理任务结束时 | 汇总子代理产出 |
| SessionStart | 会话启动时 | 初始化环境、打印自定义提示 |
| PreCompact | 上下文压缩前 | 提取关键状态、防止丢失任务上下文 |
配置入口是.claude/settings.json或~/.claude/settings.json。下面是我在一台 CI 机器上的真实配置片段:
{ "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "python3 /opt/claude-hooks/check_bash.py \"$CLAUDE_COMMAND\"" } ] } ], "Stop": [ { "hooks": [ { "type": "command", "command": "bash /opt/claude-hooks/stop_report.sh" } ] } ] } }注意matcher字段——它决定了钩子挂在哪个工具或命令上。Bash(git-*)只匹配 git 开头的命令,Bash匹配所有 Bash 工具调用。挂得越细,误伤越少。
3.2 用 PreToolUse 做自动安全检查
PreToolUse 钩子最有价值的用法,是在模型真正执行命令前做一道闸门。比如我不希望模型在非交互会话里执行破坏性的 git 操作,就写了这样一个检查脚本:
#!/usr/bin/env python3 import json import os import sys command = os.environ.get("CLAUDE_COMMAND", "") blocked_prefixes = [ "git push --force", "rm -rf /", "kill -9", ] decision = { "hookSpecificOutput": { "hookError": "", "permissionDecision": "allow", "permissionDecisionReason": "" } } for prefix in blocked_prefixes: if command.strip().startswith(prefix): decision["hookSpecificOutput"]["permissionDecision"] = "deny" decision["hookSpecificOutput"]["permissionDecisionReason"] = ( f"命令 {prefix} 被项目安全策略禁止" ) print(json.dumps(decision)) sys.exit(0) print(json.dumps(decision))这里的关键是输出格式:Hook 脚本往 stdout 打印一个 JSON,其中的hookSpecificOutput.permissionDecision可以是 allow 或 deny。deny 会让工具调用被拦截,模型会收到原因并尝试换一种实现方式。实际使用中我还加过路径校验——比如写文件时不允许模型往src/generated之外的目录输出。
这个机制的威力在于:你不是在 prompt 里"请求"模型别做某事,而是在运行机制层面让它"做不到"某事。前者是可选的,后者是强制的。
3.3 Stop 钩子的实际用途
Stop 钩子是我用得第二多的。它在一个完整任务结束(或达到最大轮次)时触发,此时环境变量里有CLAUDE_MESSAGE和CLAUDE_SUMMARY。我曾经把它接到一个内部机器人上,让 Claude Code 每次处理完需求后自动把总结发到团队群里:
#!/bin/bash summary=$(cat <<'EOF' $CLAUDE_SUMMARY EOF ) curl -s -X POST "http://internal-bot/send" \ -H "Content-Type: application/json" \ -d "{\"channel\":\"dev-report\", \"message\":\"$summary\"}"这样团队不用盯着终端,也能知道 AI 助手这次帮忙改了什么、卡在哪里。Stop 钩子特别适合做"事后处理",因为此时所有上下文都已经结算完了。
3.4 Hooks 调试踩过的坑
Hooks 看着简单,实际跑起来问题不少。我踩过三个比较典型的坑:
第一个是静默失败。Hook 脚本如果直接报错退出,Claude Code 有时候只会把错误记到日志里,对话照常继续,你以为安全策略生效了,其实根本没有。建议所有 Hook 脚本都加日志输出到固定文件,并且先手动执行一遍验证输入输出。
第二个是超时与性能。每个 Hook 都会增加一轮工具调用的延迟,如果脚本里还要起 Python 环境、连数据库查参数,一次会话能慢好几倍。我后来把所有高频 Hook 都改成纯 shell 或者 Go 编译成的单个二进制,启动开销控制在十毫秒级别。
第三个是"全盘 deny"反而更难控制。我之前把 SSH、curl 之类的命令全部拦住,结果模型开始改用 Python 的 requests 库去实现同样的事情。安全钩子最好按前缀精确匹配,而不是一刀切。你要拦的是某个具体行为,不是某一类工具。
4. 交互层改造:自定义斜杠命令与输出风格
4.1 自定义斜杠命令的制作方法
斜杠命令是提高 Claude Code 使用效率最直接的方式。它本质上是把一段精心编写的 prompt 模板固化成可复用的命令,放在.claude/commands/或~/.claude/commands/目录下,文件名去掉.md就是命令名。
我在团队项目里写过一个review命令:
--- description: 对当前改动做一轮代码走查 argument-hint: [scope] --- 请对 $1 范围内的代码改动做一次同行评审。 评审时严格按以下顺序执行: 1. 先运行 git diff $1,理解改动全貌 2. 检查是否引入了安全问题(敏感信息、越权、注入) 3. 检查是否遵守项目 CLAUDE.md 中的代码规范 4. 检查测试覆盖:新增逻辑是否有对应测试 输出要求: - 按"严重问题 / 建议改进 / 亮点"三节输出 - 每个问题都必须给出文件路径和具体行号 - 不要输出泛泛的赞美使用的时候在终端敲/review src/services,文件里的$1会被替换成src/services。除了$1到$9的位置参数,还有$ARGUMENTS表示全部参数。命令里甚至可以加@路径把其他文档一起导入上下文。
建命令的文件名要克制。我见过有人一天建二十个命令,最后自己都记不住。我的标准是:某个操作每周至少重复三次,才值得固化成命令。一次性的任务写 prompt 就行,不要制造新的心智负担。
4.2 多段命令与流程编排
自定义命令文件里可以用分隔线把一段 prompt 拆成多个逻辑段落,让模型分阶段思考。比如一个发布检查命令,可以先让它跑测试,再让它看变更集,最后输出发布风险。这种编排比一段大而全的 prompt 效果稳定得多,因为模型不会被一堆互相缠绕的要求搞晕。
另一个实用技巧是组合使用@导入和系统命令的CLAUDE.md引用。比如给某个子模块写一个独立的CHECKLIST.md,然后在斜杠命令里用@CHECKLIST.md导入,命令就永远指向最新版的检查项,不用每次改命令本身。
4.3 输出风格:改模型"说话的样子"
Claude Code 还支持通过.claude/output-styles/目录定义输出风格,并在settings.json里通过"outputStyle"字段指定。这个机制会把你定义的风格描述注入到系统提示词里,影响模型回答的形态。
比如做日常开发时,我不希望模型长篇大论解释原理,就定义了一个compact.md风格:
所有回答遵循以下规则: - 直接给出结论,先说什么改了、为什么改 - 代码块之外的解释不超过三句话 - 不输出"分析过程"类的思考过程 - 遇到不确定的地方,明确列出假设,不要暗示自己已经确定实际用下来,输出风格对体验的提升非常明显。它和 CLAUDE.md 的区别在于:CLAUDE.md 约束的是行为,输出风格约束的是表达。对 AI 编程工具来说,这两者经常需要配套使用——行为改了但表达没改,你依然会觉得它"不像那么回事"。
5. 能力层改造:MCP 与外部工具链注入
5.1 MCP 为什么是"外挂"思路
如果说 Hooks 是在现有工具链上插桩,那 MCP(Model Context Protocol)就是给模型直接接新的工具仓库。MCP 的思路是:把外部能力封装成标准协议,模型可以像调用本地函数一样调用远程或本地服务。Claude Code 作为一个支持 MCP 客户端的运行时,能动态加载这些能力。
我把它理解成一个"外挂工具栏"。默认情况下模型手里只有读文件、写文件、执行命令这些基础工具,接上 MCP 之后,它可以查询数据库、操作浏览器、读写 Redis、调用内部接口。这等于重新定义了"AI 编程工具能干什么"的边界。
5.2 给 Claude Code 接入 MCP 的实战配置
MCP 服务器的接入有两种常见 transport:stdio 和 SSE/HTTP。本地工具一般用 stdio,远程服务一般走 HTTP。常用命令是:
# 添加一个基于 stdio 的 MCP 服务器 claude mcp add my-tools --transport stdio --env KEY=value -- npx -y some-mcp-server # 查看当前已加载的服务器 claude mcp list # 移除不再需要的 claude mcp remove my-tools配置同样会写进.claude/settings.json的mcpServers字段,方便进入版本库共享。我给一个内部项目接入过 PostgreSQL 数据库的 MCP,配置长这样:
{ "mcpServers": { "pg": { "type": "stdio", "command": "npx", "args": ["-y", "@modelcontextprotocol/server-postgres"], "env": { "DATABASE_URL": "postgres://user:pass@db.internal:5432/prod" } } } }接完之后,模型在处理任务时可以直接执行 SQL 查询,不需要我手工导数据或者写临时代码。这让"改代码"和"查数据"变成同一个对话里的连续动作,效率提升非常明显。
5.3 哪些 MCP 值得装,哪些是智商税
我装过的 MCP 不少,最后长期保留的其实不多。值得装的三类:带官方语义的 git 操作、无头浏览器、数据库查询。这三类解决的都是 AI 编程工具最致命的短板——它看不到系统实际状态,只能靠猜测。让模型能直接查 git 状态、打开网页、查数据库,等于还给它一双眼睛。
不建议装的是两类:一类是"万能型"服务器,什么都能接,但每条指令都要传巨大的 schema,上下文浪费严重;另一类是玩具服务器,接一个外部天气 API 就号称"极大扩展能力",实际对开发流程毫无帮助。MCP 也会占用上下文预算。接十几个服务器,模型每次决策前都要遍历所有工具定义,反而变笨。
还要注意:MCP 服务器的质量参差不齐,很多是社区爱好者写的,错误处理做得稀烂,可能反过来干扰主流程。我的建议是先接一个跑通、观察一两天 token 消耗和误用情况,再决定是否保留或扩展。
另外提一句模型路由:Claude Code 支持通过环境变量ANTHROPIC_BASE_URL、ANTHROPIC_AUTH_TOKEN、ANTHROPIC_MODEL指向兼容的 API 端点,让这个 harness 接上其他模型服务。如果你打算这么做,先确认目标模型对工具调用(function calling)的支持程度——Mods 改造里最依赖的 tools 能力如果模型不支持,前面说的 Hooks、MCP 整套机制都会失效。
6. 综合实战:把 Claude Code 改造成团队专属的代码审查助手
6.1 需求拆解
前面说的是单项能力的拆解,真正让 Mods 发挥威力的是把它们组合起来。我拿一个实际例子说明:我们团队想用 Claude Code 做合并请求的"第一轮机器审查",目标是拦截明显的问题,减少人工 review 的负担。
需求拆解下来是四件事:
- 让模型知道这个仓库的架构和规范(记忆层)
- 提供一个标准化的审查指令(交互层)
- 在模型尝试执行危险命令时拦下来(行为层)
- 审查完成后输出结构化报告并通知团队(行为层 + 交互层)
6.2 从 CLAUDE.md 到 Hooks 的完整组装
首先在仓库根目录的CLAUDE.md里加了三条硬性约束:
- 本项目涉及用户敏感数据,禁止在任何输出中包含原始 token 或未脱敏的用户信息 - 新增依赖必须走 RFC 流程,不允许直接修改 package.json 后不说明理由 - 合并请求审查只关注代码层面,不输出主观评价然后在.claude/commands/review.md里写入标准审查命令,指定接收目标分支名作为参数。最后在settings.json里挂上两个 Hook:PreToolUse 拦截包含硬编码密钥的写文件操作,Stop 钩子把审查摘要发送到团队群。
整个组合完成之后,团队成员只需要在终端执行:
claude "/review feature/xxx"模型会加载仓库记忆、按命令模板执行 diff 分析、调用 git 工具查改动范围、最后输出一份带文件路径和行号的审查报告。
6.3 实测效果与迭代过程
第一版跑出来非常啰嗦,每条 diff 都点评一遍,报告长得没人看。我调整了输出风格文件,强制要求按"严重问题 / 建议改进 / 亮点"三节输出,每个问题必须附路径和行号。第二版好了一些,但误报还是多——它把很多正常日志里出现的数字当成敏感信息。我又在 CLAUDE.md 里补了一条说明:"日志中的 requestId 字段属于允许输出的非敏感标识"。
迭代到第三版才达到可用状态。这个过程中我发现一个规律:Mods 改造不是一次性的,它更像调参。每次只改一个变量、确认生效再动下一个,否则出了问题你根本不知道是哪条配置导致的行为偏移。我把这个过程类比成写测试——先让它在一条明确的用例上跑出预期结果,再逐渐扩大覆盖范围。
7. 改造运行机制的边界与隐患
7.1 Mods 太重的陷阱
我见过一些开发者把 Claude Code 的配置堆得非常华丽,Hooks 挂了三层、MCP 接了九个、自定义命令建了几十个。结果真正干活的时候,每次请求都要经过一堆额外检查和上下文注入,响应速度明显变慢,行为也变得不可预测。
Mods 本质上是一套运行时编排逻辑,编排越复杂,出问题的概率就越高。我的底线是:一个项目里 Hooks 不超过三四个关键场景、MCP 不超过两三个、斜杠命令控制在十个以内。超过这个阈值,维护成本就开始倒挂——你花在修配置上的时间比省下来的时间还多。
7.2 团队协作的注意点
把.claude目录纳入版本库这件事,我强烈建议做,但要做就得有纪律。个人实验性的配置先放~/.claude,确定值得共用再升级到项目目录。项目目录里的每一项配置都要配一层"注释式说明",讲清楚它解决什么问题、被谁引入、什么时候可以删。
新成员上手的时候,别让他们直接面对一堆 Hooks 和自我改造后的行为。我给团队准备的 onboarding 文档里有一张表:每个 Hook 的触发条件、matcher 范围、脚本位置、可能误伤的场景。这张表在排障时也特别有用——有一次模型行为异常,最后定位到是别人新加的一个 PostToolUse Hook 悄悄改了文件权限。
配置是有生命周期的,过时的配置比没有配置更危险。我建议每个季度做一次清理,把没有实际调用记录的 Hook 和命令全部删掉。
7.3 关于"改造运行机制"这件事的个人判断
最后说一点我自己的体会。Claude Code 开放 Mods 能力,真正改变的不是某一个功能的便利性,而是使用者和工具之间的关系。以前我们和 AI 编程工具的交互是单向的:它给我答案,我接受或拒绝。现在我可以重新定义它的行为边界、给它装上新工具、让它在特定时机做特定动作。这种从"使用工具"到"编写工具行为"的转变,给 AI 编程工具这个品类带来的可能性,比某一个具体功能重要得多。
实操层面的建议就一条:改造要像写测试一样,每次只动一个变量。先跑通最小闭环,再逐步加约束。你的第一个 Mod 不需要多复杂,哪怕只是在 CLAUDE.md 里加一条"禁止使用全局变量保存状态"然后观察它接下来会不会遵守,都已经是在体验"改造运行机制"这件事了。