Claude Code 高级用法盘点:Hook、MCP、并行 Agent、Worktree 与上下文管理

#工具 #Claude Code #AI 共 6,805 字 约 14 分钟

用 Claude Code 半年,从一开始把它当 “VSCode + Cursor 的某种替代”,到现在更像在管理一个能并行的小团队。这篇是把我自己日常真正在用的高级功能梳一遍 —— hook、MCP、subagent、worktree、CLAUDE.md 分层、/clear 时机这些。不是官方文档复述,是有取舍判断的使用者视角。读完应该能直接抄走改改就用。

0. 文章地图

8 个模块,按"先操作后理念"排:

#模块类型一句话
1Hook 系统操作在生命周期事件上挂自己的脚本
2CLAUDE.md 分层操作给 Agent 一份会跟着它跑的项目说明书
3MCP Server操作把任何外部能力包成 Agent 可调用的工具
4并行 Agent操作用 Task 工具一次派出多个 subagent
5Worktree 隔离操作git worktree 把多个 session 在物理目录上隔开
6Slash 命令 / 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主回合结束收尾通知、写一行日志
SubagentStopsubagent 回合结束给并行 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 验证”。

写什么最有效?我自己摸出来的几条:

  1. 写"模型容易踩的坑",不写"显然的规则"。 “用 React” 不用写,模型会自己看 package.json。“我们的 useFetch 必须放在 try/catch 外层因为内部已经 catch” —— 这种写。
  2. 写"一次教会、永久有效的"。 Boris Cherny 那句"compounding engineering" —— 每次 Claude 犯的错被记下来都是永久红利。把"昨天它又把 logger 引错位置了"这种小坑加进去。
  3. 每条尽量给原因。 “用 X 不用 Y” 不如"用 X 不用 Y,因为 Y 会触发 ESM/CJS 互引问题"。模型知道为什么之后泛化能力强一档。
  4. 保持精简。 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 同时干三件事,互不污染。

我自己的工作流:

  1. 复杂任务先在主 repo shift+tab 进 plan mode,跟模型把任务拆成 3-5 个正交的子任务
  2. 每个子任务开一个 worktree(命名 <repo>-<task-slug>
  3. 开三个终端窗口(用 tmux / Warp 的 split),各自跑一个 Claude
  4. 跑完之后回主 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。我个人的风格偏保守:

  1. 能拒绝的危险动作全 deny。 rm -rfgit pushgit commitcurl(防 exfil)、chmodsudo
  2. 常用读操作 allow。 git statusgit loglspwdcat、各种 test 命令。
  3. 写操作不在 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 编辑器",是一个有反射、有记忆、能并行、有边界、会休息的小团队。

具体到日常,最容易落地的三条(如果只能选三条):

  1. 每个项目至少有一份 200 行以内的 CLAUDE.md。这是最便宜的复合投资。
  2. 任何做过两次的事,第三次包成 slash 命令或 hook
  3. 超过 30 分钟的任务先 plan mode,复杂改动直接开 worktree

剩下的 MCP、subagent、自定义 server 这些是"再进一步" —— 有需要再加,没需要不要为了用而用。工具的价值在于它在你不在的时候替你工作,不在于"我用了多少种"。


参考资源: