Claude Code 高级用法盘点:Hook、MCP、并行 Agent、Worktree 与上下文管理
用 Claude Code 半年,从一开始把它当 “VSCode + Cursor 的某种替代”,到现在更像在管理一个能并行的小团队。这篇是把我自己日常真正在用的高级功能梳一遍 —— hook、MCP、subagent、worktree、CLAUDE.md 分层、
/clear时机这些。不是官方文档复述,是有取舍判断的使用者视角。读完应该能直接抄走改改就用。
0. 文章地图
8 个模块,按"先操作后理念"排:
| # | 模块 | 类型 | 一句话 |
|---|---|---|---|
| 1 | Hook 系统 | 操作 | 在生命周期事件上挂自己的脚本 |
| 2 | CLAUDE.md 分层 | 操作 | 给 Agent 一份会跟着它跑的项目说明书 |
| 3 | MCP Server | 操作 | 把任何外部能力包成 Agent 可调用的工具 |
| 4 | 并行 Agent | 操作 | 用 Task 工具一次派出多个 subagent |
| 5 | Worktree 隔离 | 操作 | git worktree 把多个 session 在物理目录上隔开 |
| 6 | Slash 命令 / Skill | 操作 | 把重复工作流固化成 /cmd |
| 7 | 权限与安全模型 | 理念 | allow/deny/skip 三档背后的取舍 |
| 8 | 上下文管理 | 理念 | /clear、/compact、context rot 怎么对付 |
操作类都附完整的、能复制就跑的片段;理念类讲的是"为什么这样设计、什么时候动手"。
1. Hook 系统:在生命周期事件上挂脚本
Hook 是 Claude Code 最低估的一个能力。它把整个会话切成一组生命周期事件,让你在每个点插入自己的脚本:
| 事件 | 触发时机 | 我用它干嘛 |
|---|---|---|
SessionStart | 会话开始 | 注入项目状态、git status、未读 issue |
UserPromptSubmit | 用户回车之后、模型读到之前 | 改写或拒绝 prompt(敏感词、注入项目元数据) |
PreToolUse | 工具调用前 | 拦截危险命令、拒绝写入特定目录 |
PostToolUse | 工具调用后 | 自动 lint、自动 test、把变更写到 changelog |
Notification | 模型等输入 | 弹通知/手机推送 |
Stop | 主回合结束 | 收尾通知、写一行日志 |
SubagentStop | subagent 回合结束 | 给并行 agent 单独计时/提醒 |
注册位置在 ~/.claude/settings.json 或者项目里的 .claude/settings.json。最常见的两种用法:
用法一:模型动作前后挂自动化。 例如每次 Edit 之后自动跑一遍 ESLint。把 lint 错误回灌到模型上下文,比"你忘了 lint 我帮你催一下"靠谱得多 —— 模型会在下一轮直接修。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "node /Users/me/.claude/hooks/lint-changed.js",
"timeout": 30
}
]
}
]
}
}
matcher 支持工具名的正则 —— Edit|Write 表示文件写入类工具触发。脚本从 stdin 拿到 hook payload(包括被改的文件路径),跑 lint、把错误打到 stdout 即可。退出码非 0 时 Claude Code 会把 stderr 当作模型输入。
用法二:把会话状态接到外部世界。 我之前写过 一篇专门讲 Mac 通知 + Bark 推送
的文章,本质就是 Stop + Notification 两个事件接一个 Python 脚本,本地弹一条 macOS 通知,远程发一条手机推送。长任务跑完不用一直盯屏幕。
路径必须绝对
hook command 的 cwd 不可预期(取决于会话当时在哪个项目里),~/... 这种波浪号也不会展开。所有路径写绝对路径。Hook 真正的强项是 它把"提醒模型做某件事"变成了"由系统强制做某件事"。指令型的提示再怎么写也会被遗忘,hook 不会。
2. CLAUDE.md:给 Agent 的项目说明书
CLAUDE.md 看起来很简单 —— 就是一份 Markdown,自动注入每次会话的 system prompt。但它的真正威力来自分层:
~/.claude/CLAUDE.md # 全局:所有项目都注入
<repo>/CLAUDE.md # 项目级:cd 到这个 repo 才注入
<repo>/<subdir>/CLAUDE.md # 子目录级:进入子目录的工作才注入
合并顺序是从外到内,越靠内的优先级越高。结合用法:
- 全局 写"我是谁、偏好什么"。例如"我是 Jason,写代码时优先函数式风格",“中文回复偏正式术语,避免口语化译法”。
- 项目级 写"这个 repo 的隐式规则"。例如"用 pnpm 不用 npm"、“提交前必须跑
pnpm test"、“Tailwind 颜色只用tokens.ts里定义的,不准bg-red-500"。 - 子目录级 写"这块代码的局部约定”。例如
apps/api/CLAUDE.md写"所有 endpoint 必须有 zod schema 验证”。
写什么最有效?我自己摸出来的几条:
- 写"模型容易踩的坑",不写"显然的规则"。 “用 React” 不用写,模型会自己看 package.json。“我们的 useFetch 必须放在 try/catch 外层因为内部已经 catch” —— 这种写。
- 写"一次教会、永久有效的"。 Boris Cherny 那句"compounding engineering" —— 每次 Claude 犯的错被记下来都是永久红利。把"昨天它又把 logger 引错位置了"这种小坑加进去。
- 每条尽量给原因。 “用 X 不用 Y” 不如"用 X 不用 Y,因为 Y 会触发 ESM/CJS 互引问题"。模型知道为什么之后泛化能力强一档。
- 保持精简。 CLAUDE.md 每次会话都加载,吃上下文。我个人的红线是单文件不超过 200 行。超过就考虑拆到子目录或 slash command 按需加载。
一份我自己在用的项目级模板片段:
# Project: lifelog
## Stack
- Hugo (extended) + 自定义主题 inkstone (git submodule)
- 内容在 content/posts/,中英双语:foo.md + foo.en.md
- 通过 translationKey 关联两版
## 写作约定
- 第一人称、有取舍判断
- 引言用 blockquote 起手
- 长文在第 0 节给文章地图
- 避免口语化译法(专业术语用正式中文,不用流行翻译腔)
## 操作约定
- 启动本地预览:`hugo server -D`
- 提交前跑:`hugo --minify` 确保没有报错
- 永远不直接 `git push`,让我自己来
最后一条是配合下面 §7 的权限模型用的 —— Agent 看到这条会克制自己。
3. MCP Server:把外部能力包成工具
MCP(Model Context Protocol)是 Anthropic 开源的协议,作用是让任何外部工具/数据源都能以统一的方式接入到 Claude(或者其他兼容 client)。Anthropic 自己有一句话:MCP 之于 LLM 等价于 USB-C 之于硬件。
Claude Code 默认就内置了一些(filesystem、bash、web fetch),但真正解锁场景的是自定义 MCP server。三个原语对应 Agent 的三种"知道方式":
| 原语 | 含义 | 类比 |
|---|---|---|
| Tools | 模型可以调用的动作 | 函数 |
| Resources | 模型可以读取的数据 | 文件 |
| Prompts | 用户可以触发的模板 | 快捷指令 |
注册一个 MCP server 到 Claude Code 最快的路径是用 CLI:
# 加一个本地 stdio server
claude mcp add my-tools \
--command "node /Users/me/mcp-servers/my-tools/index.js"
# 加一个 HTTP server
claude mcp add notion \
--url "https://mcp.example.com/notion" \
--header "Authorization: Bearer xxx"
# 看现在挂了哪些
claude mcp list
写一个最小的自定义 server 也不复杂。下面是一个"读 Linear issue"的最小骨架(Node.js + 官方 SDK):
// index.js
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
const server = new Server(
{ name: "linear-mini", version: "0.1.0" },
{ capabilities: { tools: {} } }
);
server.setRequestHandler("tools/list", async () => ({
tools: [
{
name: "get_issue",
description: "Fetch a Linear issue by identifier (e.g. ENG-123)",
inputSchema: {
type: "object",
properties: { id: { type: "string" } },
required: ["id"],
},
},
],
}));
server.setRequestHandler("tools/call", async (req) => {
if (req.params.name !== "get_issue") throw new Error("unknown tool");
const { id } = req.params.arguments;
const r = await fetch(`https://api.linear.app/graphql`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: process.env.LINEAR_API_KEY,
},
body: JSON.stringify({
query: `query($id:String!){ issue(id:$id){ title description state { name } } }`,
variables: { id },
}),
});
const data = await r.json();
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
});
await server.connect(new StdioServerTransport());
接进 Claude Code 之后,会话里直接说"看一下 ENG-123 这条 issue",模型就会调 get_issue。
我的取舍判断:
- 能用现成 MCP 别自己写。 GitHub、Notion、Slack、Postgres 这些都有官方/社区维护的 server,先去 modelcontextprotocol.io 翻一遍。
- 自定义 MCP 适合"私有 / 内部 / 跨工具"的能力。 例如把公司内部的状态板、自己的笔记库、家里的 NAS 接进来。
- MCP server 启动失败不会致命,但会降级。Claude Code 启动时如果 server 跑不起来,整个会话还是能跑,只是那个工具不可用。所以可以放心多挂几个。
4. 并行 Agent:一次派多路 subagent
Claude Code 内置了一个 Task 工具,可以在主会话里派出 subagent 并行干活。这不是噱头,是真正改变工作节奏的能力。
典型场景:
- 大重构:把"重命名 X 到 Y"拆成"找所有引用"、“改 src/"、“改 test/"、“改 docs/“四路并行
- 多技术栈对照:让两个 subagent 分别用 React 和 Vue 实现同一个组件,回来对比
- 代码审查:派一个 subagent 看安全、一个看性能、一个看可读性
调用方式在主会话里就是说一句类似的话:“请并行启动 3 个 subagent,分别检查 src/api/、src/components/、src/hooks/ 三个目录的类型错误,把结果汇总。“模型会自动用 Task 工具 fan out。
如果想固化成标准流程,可以在 .claude/agents/ 下定义命名 subagent:
<!-- .claude/agents/security-reviewer.md -->
---
name: security-reviewer
description: 专注于安全漏洞的代码审查 agent
tools: Read, Grep, Glob, Bash
---
你是一个专注于 OWASP Top 10 的代码审查者。
扫到的每条问题按以下格式输出:
- **风险等级**: Critical / High / Medium / Low
- **位置**: file:line
- **类型**: SQL Injection / XSS / SSRF / ...
- **修复建议**: 具体改法
只看安全问题,不要给样式或性能反馈。
之后在主会话里"用 security-reviewer 子代理审一下 src/api/“就会精准触发。tools: 字段限制 subagent 能用的工具(不给它 Edit/Write 就让它只读)。
几条用了一阵之后的体会:
- 并行的极限大概在 5-8 个。再多 token 成本和上下文切换成本就上来了。
- subagent 不共享主会话的上下文。它只看自己的 prompt 和能调到的工具。所以指令必须自包含。
- 结果的合并是主 agent 的活。 主 agent 拿回 N 份输出之后还要再做一轮整合,这一步的质量很大程度决定了并行的最终价值。
- 不是所有任务都适合并行。强耦合的任务(A 的输出是 B 的输入)拆成并行反而更慢。判断标准:能不能写成"互不影响、独立完成"的若干份子任务。
5. Worktree 隔离:让多个 session 物理上分开
并行的另一种粒度是多个 Claude Code 主会话并行跑在同一个 repo 上。最大的坑是:它们都改同一个 working tree,互相覆盖。
git worktree 是为这个量身打造的:
# 主 repo 在 ~/code/myapp(在 main 分支上正常开发)
cd ~/code/myapp
# 派生两条独立分支的工作目录
git worktree add ../myapp-feat-a -b feat/a
git worktree add ../myapp-feat-b -b feat/b
# 现在物理上有 3 个目录:
# ~/code/myapp (main)
# ~/code/myapp-feat-a (feat/a)
# ~/code/myapp-feat-b (feat/b)
# 共享同一个 .git,但工作树完全隔离
然后开三个终端,各自 cd 到对应目录、claude 启动会话。三个 agent 同时干三件事,互不污染。
我自己的工作流:
- 复杂任务先在主 repo
shift+tab进 plan mode,跟模型把任务拆成 3-5 个正交的子任务 - 每个子任务开一个 worktree(命名
<repo>-<task-slug>) - 开三个终端窗口(用 tmux / Warp 的 split),各自跑一个 Claude
- 跑完之后回主 repo,用
git worktree list检查,挨个 review、merge、git worktree remove
git worktree list # 看现在有哪些 worktree
git worktree remove ../myapp-feat-a # 干完拆掉
git worktree prune # 清理过期引用
几条经验:
- 每个 worktree 用独立分支。在同一个分支上开两个 worktree 会被 git 拒绝(这是好事)。
- node_modules / .venv 不共享。每个 worktree 都要重新装。可以用 pnpm 的 store 或者 venv 的 symlink 缓解,但代价是配置变复杂,看权衡。
- 跨 worktree 的环境变量、
.env不会自动复制。需要的话cp或者 symlink。 - worktree 不是 fork。所有 worktree 共享同一个
.git/,所以git fetch一次全更新,commit 也是同一个 object database。这是它比git clone多份快的原因。
worktree + 并行 subagent 是两种不同的并行:subagent 是"在一个会话里 fan out”,worktree 是"多个独立会话物理隔离”。前者快但耦合,后者慢但干净。我一般在重构 / 跨模块改动时用 worktree,在调研 / 检查 / 生成类任务里用 subagent。
6. Slash 命令 / Skill:把工作流固化
/command 是 Claude Code 的另一个 compounding 投资点。任何你做过两次的工作流,第三次开始就该考虑把它变成 slash 命令。
定义位置:
~/.claude/commands/<name>.md # 全局,所有项目都能用
<repo>/.claude/commands/<name>.md # 项目级
文件本身就是一份 Markdown,会作为 prompt 注入。下面是我用的 /review 命令:
<!-- .claude/commands/review.md -->
---
description: 对当前分支 vs main 的 diff 做一轮自审
allowed-tools: Bash(git diff:*), Bash(git log:*), Read
---
请对当前分支相对于 main 的所有变更做一轮代码自审:
1. 跑 `git diff main...HEAD --stat` 看改了哪些文件
2. 跑 `git diff main...HEAD` 看具体内容
3. 按以下角度给反馈:
- 有没有引入复杂度但没价值的抽象
- 有没有破坏既有约定(参考 CLAUDE.md)
- 测试覆盖是否够(重点看新增 public 函数)
- 是否有遗留的 console.log、debugger、TODO
最后给出一份 markdown 形式的审查报告,可以直接贴进 PR。
会话里 /review 一敲就跑完整套。allowed-tools 字段限定只允许跑 git diff/log 类命令 —— 即使不在全局 allowlist 里也临时放行,保证安全。
支持参数也很简单,文件里用 $ARGUMENTS 占位:
<!-- .claude/commands/explain.md -->
请详细解释 $ARGUMENTS 这段代码做了什么、为什么这样写、有没有更简单的写法。
会话里 /explain src/utils/debounce.ts 就会把路径替换进去。
我现在常驻的几条命令:
| 命令 | 用途 |
|---|---|
/review | 自审当前分支 |
/plan | 把一段需求拆成 todo 列表,等我审完再开干 |
/explain <path> | 解释一段代码 |
/tidy | 跑 lint + format + 修明显的 type 错误 |
/post | 这个博客专用 —— 帮我起一个新文章的 front matter + 大纲 |
Skill(更广义的"打包能力”)目前还在演进,但核心思路是一样的:把"反复需要解释给 Claude 的工作流"变成"一行就能触发的资产”。每条都是永久红利。
7. 权限与安全模型:allow / deny / skip 三档
Claude Code 的权限系统是它跟普通"AI 编辑器"最大的区别之一。简单说有三档:
| 模式 | 行为 | 我什么时候用 |
|---|---|---|
| Default (ask) | 每个工具调用都问 | 处理陌生项目、第一次用某 MCP |
| Auto-accept | 在 allowlist 里的工具自动通过 | 日常 |
| Bypass / dangerouslySkipPermissions | 全部放行 | 沙盒里跑 / 一次性脚本 |
settings.json 里的 permissions 块是最常调的地方:
{
"permissions": {
"allow": [
"Bash(npm test:*)",
"Bash(pnpm:*)",
"Bash(git status)",
"Bash(git diff:*)",
"Bash(git log:*)",
"Edit",
"Write",
"Read"
],
"deny": [
"Bash(rm -rf:*)",
"Bash(chmod:*)",
"Bash(curl:*)",
"Bash(git push:*)",
"Bash(git commit:*)"
]
}
}
deny 优先于 allow。我个人的风格偏保守:
- 能拒绝的危险动作全 deny。
rm -rf、git push、git commit、curl(防 exfil)、chmod、sudo。 - 常用读操作 allow。
git status、git log、ls、pwd、cat、各种 test 命令。 - 写操作不在 allowlist 里。
Edit/Write我反而留默认 ask —— 模型每次写文件都会再问一下,体感慢一点但底线很稳。
--dangerously-skip-permissions(启动参数)是核武器:所有权限检查全跳过。它不是给日常用的,是给以下场景:
- Docker / VM 沙盒里,反正搞坏了重启就没了
- 一次性脚本任务,例如批量重命名 1000 个文件,全程在自己看着的情况下跑
- CI / Headless 跑,整个环境本来就是临时的
千万别在自己的工作环境里日常开 dangerouslySkipPermissions。代价是 —— 如果模型某次产生幻觉决定 rm -rf ~,你会哭。
更稳的做法是用 沙盒:把 Claude 跑在 devcontainer / Docker / Multipass VM 里,物理上隔离。Anthropic 自己的 安全最佳实践文档 就推荐了这种用法。
权限模型的真正用途是让 Agent 在合理范围内自主,越界时强制停下来问。allow 太松会被坑,deny 太多会变成每步都问的 “co-pilot 模式” 失去 agentic 价值。这个度需要项目和个人风格双重平衡。
8. 上下文管理:context 是 king,但会腐烂
最后一条,理念为主。Andrej Karpathy 那句被引爛的话:
“Context engineering is the delicate art and science of filling the context window with just the right information for the next step.”
而 Anthropic 自家的研究反复确认了一个反直觉的事:上下文不是越多越好。模型在长上下文下会出现"context rot”(上下文腐烂)—— 即使是简单任务,输入越长性能越下滑。
所以 Claude Code 提供了几把刀:
| 命令 | 行为 | 什么时候用 |
|---|---|---|
/clear | 清空上下文重启 | 切换到完全无关的任务 |
/compact | 让模型自己把当前对话压缩成摘要 | 任务相关、但已经堆积太多废话 |
/resume | 恢复之前 compact 掉的细节 | 偶尔需要回头细看 |
我的使用习惯:
- 任务边界是
/clear的天然时机。 写完 feature A 要修 bug B,先/clear。 - 跑了 30+ 轮还在同一任务上、感觉模型开始"漂”,先
/compact。 体感上/compact之后模型会重新聚焦。 - CLAUDE.md 是"永久上下文”,不要让它变成废话仓库。 每条都该是高频用到的。
- 真正长的参考资料别贴对话里,写到文件让模型 Read。
Read是按需加载,不贴整段 paste 进 prompt。
更深一层的理念是 subagent 是上下文管理的工具。每派一个 subagent 出去,相当于给那个子任务一个干净的上下文窗口;它跑完只把摘要回传给主 agent。这就是为什么大重构用 subagent 比"主 agent 一个人改"更快 —— 不是因为并行省时间,是因为每个子上下文都更干净。
跟权限模型一样,上下文管理是个"时机感"问题,没有死规则。原则只有一条:当你感觉模型开始重复、绕圈、忘事,就该动手了。这个直觉是用出来的。
9. 收束:从工具到团队
写完这 8 节,回头看共同的主线其实是:
Claude Code 最值钱的不是它能写代码,是它能被你配置成你工作方式的一部分。
Hook 是反射弧,CLAUDE.md 是长期记忆,MCP 是感官扩展,subagent 是分身,worktree 是物理隔离,slash 命令是肌肉记忆,权限模型是边界,上下文管理是节制 —— 这些组合起来,你管理的就不再是"一个 AI 编辑器",是一个有反射、有记忆、能并行、有边界、会休息的小团队。
具体到日常,最容易落地的三条(如果只能选三条):
- 每个项目至少有一份 200 行以内的 CLAUDE.md。这是最便宜的复合投资。
- 任何做过两次的事,第三次包成 slash 命令或 hook。
- 超过 30 分钟的任务先 plan mode,复杂改动直接开 worktree。
剩下的 MCP、subagent、自定义 server 这些是"再进一步" —— 有需要再加,没需要不要为了用而用。工具的价值在于它在你不在的时候替你工作,不在于"我用了多少种"。
参考资源:
- Claude Code 官方文档
- MCP 协议主页
- Anthropic Engineering: Claude Code Best Practices
- How Anthropic Teams Use Claude Code
- 我之前写的 给 Claude Code 接上 Mac 通知 + Bark 推送
- 我之前写的 CS146S 课程笔记 (其中 Week 3 / Week 4 跟本文重叠最多)