This is the full developer documentation for AI 技术图谱
# AI 技术图谱
> AI 编程工具配置与 agent 工程细节:具体到字段名、命令、报错原文,以及「怎么确认它生效了」。
[](#)
这个站收集 AI 编程工具和 agent 工程里那些具体到字段名的细节:配置怎么写、报错原文长什么样、改完怎么验证它真的生效了。每篇独立成立,顶部标适用工具、前置假设和最后核对日期;过时的内容不删,在原位置加更新说明。
不做零基础铺垫。从零开始学 AI 编程的读者先去 [《验收先行》](https://ai-coding-from-zero.pages.dev);已经会碰命令行、想查具体配置的人,直接按分支进。
## 按分支阅读
[Section titled “按分支阅读”](#按分支阅读)
[**Agent 工程**](/agents/)
[上下文工程、skills、subagent、定时任务:让 agent 跑得久、不跑偏的具体配置。](/agents/)
[已有 7 篇](/agents/)[**Claude Code 专题**](/claude-code/)
[配置优先级、hooks、权限模式、MCP、成本控制、CI 集成,以及区域限制报错。](/claude-code/)
[已有 7 篇](/claude-code/)[**速查与索引**](/reference/)
[按报错原文查定位方向,以及全站待核实清单——哪些结论是硬的、哪些是软的。](/reference/)
[已有 2 篇](/reference/)
## 这个站怎么用
[Section titled “这个站怎么用”](#这个站怎么用)
**搜报错进来的**
报错原文在正文里整段保留,按页内目录直接跳到对应段落。
**按分支逛的**
左侧栏按技术分支分组,分支之间没有依赖,从哪篇开始都行。
**追更新的**
[RSS](/rss.xml) 订阅新篇;[llms-full.txt](/llms-full.txt) 可以把全站喂给 AI 问问题。
## 写了多少
[Section titled “写了多少”](#写了多少)
38,523 全站字符(估算)
31,968 中文字
121 分钟 预计阅读时长
16 篇文章
写作中当前状态
最新几篇:[CLAUDE.md 上下文工程](/agents/claude-md-context/)、[skills 完全指南](/agents/skills/)、[subagent 完全指南](/agents/subagents/)、[hooks 完全指南](/claude-code/hooks/)、[六种权限模式](/claude-code/permission-modes/)、[接 MCP 服务器](/claude-code/mcp-servers/)。查报错去[报错原文索引](/reference/error-index/)。想看什么主题,到 [GitHub Issue](https://github.com/tobenot/ai-atlas/issues/new) 点菜。
# 关于这个站
> 关于 AI 技术图谱的作者、内容生产方式、反馈入口和催更渠道。
## 关于作者
[Section titled “关于作者”](#关于作者)
我是 **tobenot**,写代码,也写教程。另有一本面向零基础读者的[《验收先行》](https://ai-coding-from-zero.pages.dev),讲怎么从真实重复劳动出发,用 AI 做出真正能用的小工具。这个站是它的姊妹篇:那边教入门,这边收进阶的、具体到字段名的技术细节。
## 内容是怎么写出来的
[Section titled “内容是怎么写出来的”](#内容是怎么写出来的)
每篇依据官方文档和本机实测写成,AI 参与检索、核对和整理,未经核实的内容不进正文。每篇顶部标「最后核对日期」;发现过期或有误,在原位置加更新说明,旧结论保留,方便判断「什么时候变的」。
## 联系方式
[Section titled “联系方式”](#联系方式)
* GitHub Issue(推荐):[提一个](https://github.com/tobenot/ai-atlas/issues/new)。错字、过期内容、想看的主题,都从这里来。
* 每页底部有「提个 Issue」按钮,会自动带上当前页地址,反馈具体某篇用它最快。
## 催更 / 赞助[]()
[Section titled “催更 / 赞助 ”](#催更--赞助)
目前没有开通赞助渠道。想催更或支持这个站,最实在的几种方式:
* 在 [GitHub 仓库](https://github.com/tobenot/ai-atlas)点一个 Star
* 把具体某篇分享给需要它的人
* 提 Issue 说想看的主题,点菜会直接影响更新顺序
***
写到这里,谢谢看到这一页的人。
# Agent 工程
> 让 agent 跑得久、不跑偏、上下文不被撑爆,需要配的都是些具体的字段和命令。
[CLAUDE.md 上下文工程 ](./claude-md-context/)四层加载顺序、200 行上限的理由、@import 为什么不省上下文、压缩后哪些指令还在
[skills 完全指南 ](./skills/)SKILL.md 全字段、谁能调用它、以及为什么内容进了上下文就不走了
[subagent 完全指南 ](./subagents/)上下文隔离到什么程度、fork 和普通 subagent 差在哪、三种限额各管什么
[自动压缩阈值配置 ](./auto-compact-window/)autoCompactWindow 怎么设,以及怎么验证它真的触发了
[loop 命令怎么用 ](./loop-agent/)/loop 的三种写法、时间参数格式、7 天过期规则,以及别名 /proactive 是否真的存在
[/goal 命令怎么用 ](./goal-command/)给一个完成条件,让 Claude 自己跑到达标;没有固定间隔和 7 天过期上限
[跑很久该用哪个机制 ](./long-running-tasks/)/loop、/goal、Desktop scheduled tasks、Routines 四者的边界和怎么选
# Claude Code 自动压缩阈值配置:autoCompactWindow 怎么设,以及怎么验证它真的触发了
> 配置 CLAUDE_CODE_AUTO_COMPACT_WINDOW(或 settings.json 里的 autoCompactWindow)调整上下文自动压缩的触发点,并用调低阈值直接观察触发的方法验证配置生效。
适用范围
适用工具:Claude Code CLI。最后核对日期:2026-07-29。前置假设:已安装 Claude Code,知道自己的用户级配置目录在哪(`~/.claude/`,Windows 下是 `C:\Users\<用户名>\.claude\`;部分内部发行版本目录名不同,见下文踩坑提示)。
## 结论
[Section titled “结论”](#结论)
上下文自动压缩(auto-compact)的阈值可以调。设置环境变量 `CLAUDE_CODE_AUTO_COMPACT_WINDOW`,或在 `settings.json` 里写 `autoCompactWindow` 字段,把值设成目标 token 数。触发点不是这个数字本身,是这个数字的某个百分比:Sonnet 5 默认在原生窗口的约 96.7% 处触发压缩,设置这个值等于告诉 Claude Code「把窗口当作这么大来算百分比」。
## 两种配置方式
[Section titled “两种配置方式”](#两种配置方式)
### 环境变量:`CLAUDE_CODE_AUTO_COMPACT_WINDOW`
[Section titled “环境变量:CLAUDE\_CODE\_AUTO\_COMPACT\_WINDOW”](#环境变量claude_code_auto_compact_window)
官方文档(`env-vars` 页)直接给出这个变量:
> Set the context capacity in tokens used for auto-compaction calculations… Use a lower value like 500000 on a 1M model to treat the window as 500K for compaction purposes.
`model-config` 页里 Sonnet 5 一节说得更具体:
> Sessions auto-compact before the window fills, at about 967K tokens by default; set `CLAUDE_CODE_AUTO_COMPACT_WINDOW` to choose a different threshold.
这条是官方公开承诺的行为,写进 shell 配置或启动脚本都可以:
```bash
export CLAUDE_CODE_AUTO_COMPACT_WINDOW=400000
```
### `settings.json` 字段:`autoCompactWindow`
[Section titled “settings.json 字段:autoCompactWindow”](#settingsjson-字段autocompactwindow)
用户级配置文件(`~/.claude/settings.json`)里也能写同一个设置,字段名是 camelCase 的 `autoCompactWindow`:
```json
{
"autoCompactWindow": 400000
}
```
这个字段本身在公开文档的正文里没找到直接说明拼写的段落,官方文档明确写出来的开关是 `autoCompactEnabled`(控制自动压缩整体开不开),`autoCompactWindow` 的存在和行为是通过本机配置文件的内部 schema(字段描述为「Auto-compact window size」,类型 integer,取值范围 100000–1000000)和实际触发测试确认的,不是逐字抄自某一页官方文档。两种写法(环境变量 / 配置文件字段)设的是同一个底层参数,改一处就够。
踩坑预警
Windows 下的官方默认路径是 `~/.claude/settings.json`。如果用的是内部打包或二次分发的发行版本,目录名可能被重命名(例如某些环境里是 `.tclaude` 而不是 `.claude`),配置字段和行为一致,路径要按自己机器上实际安装的目录确认,别直接抄别人的路径。
## 触发点不是这个数字,是它的某个百分比
[Section titled “触发点不是这个数字,是它的某个百分比”](#触发点不是这个数字是它的某个百分比)
为什么是这样?
`autoCompactWindow` / `CLAUDE_CODE_AUTO_COMPACT_WINDOW` 设的是压缩计算用的容量基准,不是「到了这个 token 数就立刻压缩」的精确开关。Sonnet 5 默认在原生窗口(1M 模型即 1,000,000 token)的约 96.7% 处触发,也就是约 967K token。把这个基准设成 400000,触发点会落在 400000 的同一个百分比附近,即 38 万到 39 万 token 区间,早于模型原生窗口,让上下文在变得太长之前先整理一次。如果只信文档描述就假设「设成 400000 就是精确到 400000 触发」,会和实际观察到的行为有出入。
## 怎么确认它真的生效了
[Section titled “怎么确认它真的生效了”](#怎么确认它真的生效了)
读完文档不代表配置已经生效,配置是按会话(session)读取的,改完文件、当前会话不会立刻应用,需要开一个新会话才会重新读取 `settings.json`。验证方法是把阈值故意调得很低,直接观察压缩是否被触发,而不是等到真的用到接近原生窗口时才发现配置没生效:
```json
{
"autoCompactWindow": 100000
}
```
把值改成 100000 后开一个新会话(同目录下 `claude --resume` 接上原会话也可以触发重新读取配置)。如果当前上下文已经接近或超过 100000 × 96.7% 这个区间,新会话启动或发下一条消息时应该能立刻看到压缩发生。实测中,重开会话后的系统提示里出现了 `SessionStart:compact` 相关的 hook 触发记录,这是压缩已经发生的直接证据,不需要靠肉眼判断上下文变短了没有。
确认触发之后,把值改回目标数值(例如 400000),验证到此结束:
```json
{
"autoCompactWindow": 400000
}
```
## 相关但更激进的开关:`CLAUDE_AUTOCOMPACT_PCT_OVERRIDE`
[Section titled “相关但更激进的开关:CLAUDE\_AUTOCOMPACT\_PCT\_OVERRIDE”](#相关但更激进的开关claude_autocompact_pct_override)
如果不想改容量基准,只想改触发用的百分比本身,公开资料里出现过 `CLAUDE_AUTOCOMPACT_PCT_OVERRIDE` 这个变量。据反馈,它只能把默认百分比调低,不能调高,这一条行为细节来自社群交流而非独立核实,标注在这里作为线索,正式依赖它之前建议按前一节的方法自己测一遍。
## 还没确认的点
[Section titled “还没确认的点”](#还没确认的点)
* `autoCompactWindow` 这个 camelCase 拼写只在本机配置文件的内部 schema 里见到,没有在官方文档正文里逐字确认过,字段语义可能随版本变化而不通知。
* `CLAUDE_AUTOCOMPACT_PCT_OVERRIDE` 的「只能调低不能调高」尚未独立验证。
* 96.7% 这个百分比是 Sonnet 5 文档给出的默认值,其他模型或未来版本是否相同没有逐一核对。
## 参考
[Section titled “参考”](#参考)
* [Claude Code 环境变量文档](https://code.claude.com/docs/en/env-vars)
* [Claude Code 模型配置文档](https://code.claude.com/docs/en/model-config)
* [Claude Code 设置文档](https://code.claude.com/docs/en/settings)
# CLAUDE.md 上下文工程:加载顺序、200 行上限、以及为什么 @import 不省上下文
> CLAUDE.md 的四层加载位置与拼接顺序、@path 导入的展开时机与深度限制、.claude/rules/ 的路径条件加载,以及压缩之后哪些指令会被重新注入。
适用范围
适用工具:Claude Code CLI(v2.1.x 系列)。最后核对日期:2026-07-30。核实来源:官方 memory 文档。前置假设:已经在用 CLAUDE.md,遇到过「它好像没听」的情况。
## 结论
[Section titled “结论”](#结论)
三句话决定了 CLAUDE.md 该怎么写:
1. **它是上下文,不是配置**。内容作为系统提示之后的一条用户消息送达,模型会读、通常会遵守,但没有强制保证。要「一定发生」的事情写 [hook](/claude-code/hooks/)。
2. **每个会话都全量加载,不管多长**。所以每一行都是持续的 token 成本,目标是每个文件 200 行以内。
3. **`@import` 不省上下文**。导入的文件在启动时一样被展开加载。它帮的是组织,不是预算。
## 四层加载位置
[Section titled “四层加载位置”](#四层加载位置)
按加载顺序,从范围最广到最具体:
| 作用域 | 位置 | 共享给谁 |
| ---------- | ------------------------------------- | ----------------------- |
| managed 策略 | 系统目录下的 `CLAUDE.md`(各平台路径不同) | 组织全体 |
| 用户 | `~/.claude/CLAUDE.md` | 你自己,所有项目 |
| 项目 | `./CLAUDE.md` 或 `./.claude/CLAUDE.md` | 团队,通过版本控制 |
| 本地 | `./CLAUDE.local.md` | 你自己,当前项目(记得加 gitignore) |
managed 策略位置:
* macOS:`/Library/Application Support/ClaudeCode/CLAUDE.md`
* Linux 和 WSL:`/etc/claude-code/CLAUDE.md`
* Windows:`C:\Program Files\ClaudeCode\CLAUDE.md`
这一层无法被个人设置排除,`claudeMdExcludes` 也排不掉它。
## 拼接顺序:为什么「越近的读得越晚」重要
[Section titled “拼接顺序:为什么「越近的读得越晚」重要”](#拼接顺序为什么越近的读得越晚重要)
Claude Code 从当前工作目录**往上**走目录树,逐级检查 `CLAUDE.md` 和 `CLAUDE.local.md`。
在 `foo/bar/` 里启动会加载 `foo/bar/CLAUDE.md`、`foo/CLAUDE.md` 以及它们旁边的 `CLAUDE.local.md`。所有找到的文件是**拼接**进上下文,不是互相覆盖。
顺序是从文件系统根往工作目录方向。所以 `foo/CLAUDE.md` 出现在 `foo/bar/CLAUDE.md` 之前——离你启动位置越近的指令,被读得越晚。
每个目录内部,`CLAUDE.local.md` 追加在 `CLAUDE.md` 之后。所以你的个人笔记是该层级最后被读到的东西。
为什么顺序值得关心?
因为「拼接」意味着冲突不会被自动解决。两个层级对同一件事给了不同指示,模型可能任意选一个。
顺序在这时是唯一的线索:后读到的内容在注意力上通常更占优。但这只是倾向,不是规则。真正的解法是消除冲突,而不是靠顺序去压制。
所以在 monorepo 里定期检查各层 CLAUDE.md 有没有互相矛盾的条目,比写得更详细更有用。
工作目录**下面**的子目录里的 `CLAUDE.md` 不在启动时加载。它们在 Claude 读取那些子目录里的文件时才被带进来。
monorepo 里被其他团队的 CLAUDE.md 干扰时,用 `claudeMdExcludes`:
```json
{
"claudeMdExcludes": [
"**/monorepo/CLAUDE.md",
"/home/user/monorepo/other-team/.claude/rules/**"
]
}
```
模式按绝对路径用 glob 语法匹配,各层配置的数组会合并。
## 一个省 token 的小技巧
[Section titled “一个省 token 的小技巧”](#一个省-token-的小技巧)
块级 HTML 注释在内容注入模型上下文之前会被剥掉:
```markdown
## 构建命令
...
```
给人类维护者的备注写在 HTML 注释里,不消耗上下文 token。代码块内部的注释会被保留。
用 Read 工具直接打开 CLAUDE.md 文件时注释仍然可见——剥离只发生在自动注入的路径上。
## @import 的真实作用
[Section titled “@import 的真实作用”](#import-的真实作用)
```markdown
See @README for project overview and @package.json for available npm commands.
# Additional Instructions
- git workflow @docs/git-instructions.md
```
关键事实:**导入的文件在启动时和引用它的 CLAUDE.md 一起被展开加载进上下文**。
所以拆成 `@import` 对上下文预算毫无帮助。它的价值只在文件组织:让一个大文件变成几个小文件,方便维护。
规则细节:
* 相对路径和绝对路径都可以。相对路径相对**包含 import 的那个文件**解析,不是相对工作目录。
* 导入的文件可以递归导入,最大深度 4 跳。
* 解析会跳过 Markdown 代码跨度和围栏代码块。想在 CLAUDE.md 里提到一个路径而不导入它,用反引号包起来:`` `@README` `` 是字面文本,反引号外的 `@README` 会导入文件。
### 外部导入的批准对话框
[Section titled “外部导入的批准对话框”](#外部导入的批准对话框)
项目层内存文件里的 import,如果路径解析到你工作目录之外,算作**外部导入**。
Claude Code 第一次遇到项目里的外部导入时会弹一个对话框列出这些文件。拒绝之后导入保持禁用,对话框不再出现。
这个对话框防的是别人提交到共享项目里的文件。用户作用域内存文件里的 import(`~/.claude/CLAUDE.md`、`~/.claude/rules/`)是你自己写的,不弹对话框。
### 跨 worktree 共享个人指令
[Section titled “跨 worktree 共享个人指令”](#跨-worktree-共享个人指令)
`CLAUDE.local.md` 被 gitignore 之后只存在于你创建它的那个 worktree 里。要在多个 worktree 之间共享个人指令,从家目录导入:
```markdown
# Individual Preferences
- @~/.claude/my-project-instructions.md
```
## AGENTS.md 怎么办
[Section titled “AGENTS.md 怎么办”](#agentsmd-怎么办)
Claude Code 读 `CLAUDE.md`,不读 `AGENTS.md`。
仓库已经在用 `AGENTS.md` 给别的编码 agent 用的话,建一个 CLAUDE.md 导入它,两边读同一份内容不重复:
```markdown
@AGENTS.md
## Claude Code
Use plan mode for changes under `src/billing/`.
```
不需要加 Claude 专属内容的话,符号链接也行:
```bash
ln -s AGENTS.md CLAUDE.md
```
Windows 上创建符号链接需要管理员权限或开发者模式,所以用 `@AGENTS.md` 导入更省事。
`/init` 会读 Cursor 规则(`.cursor/rules/` 或 `.cursorrules`)和 Copilot 规则(`.github/copilot-instructions.md`),把相关部分并进生成的 CLAUDE.md。设了 `CLAUDE_CODE_NEW_INIT=1` 之后它还会读 `AGENTS.md`、`.devin/rules/`、`.windsurf/rules/`、`.clinerules`。
## 真正能省上下文的:.claude/rules/ 的条件加载
[Section titled “真正能省上下文的:.claude/rules/ 的条件加载”](#真正能省上下文的clauderules-的条件加载)
`.claude/rules/` 目录下的 markdown 文件,每个文件一个主题:
```plaintext
your-project/
├── .claude/
│ ├── CLAUDE.md
│ └── rules/
│ ├── code-style.md
│ ├── testing.md
│ └── security.md
```
没有 `paths` frontmatter 的规则在启动时加载,优先级与 `.claude/CLAUDE.md` 相同。
**有 `paths` 的规则只在 Claude 处理匹配文件时才加载**——这是它和 `@import` 的本质区别:
```markdown
---
paths:
- "src/api/**/*.ts"
---
# API Development Rules
- All API endpoints must include input validation
- Use the standard error response format
```
触发时机是 Claude **读取**匹配的文件,不是每次工具调用。
支持花括号展开:
```yaml
paths:
- "src/**/*.{ts,tsx}"
- "lib/**/*.ts"
```
每个花括号组会成倍增加展开后的模式数量:`src/*.{ts,tsx}` 展开成两个,`{a,b}/{c,d}/*.{ts,tsx}` 展开成八个。一条规则的整个 `paths` 列表共享 1000 个展开模式、4 MiB 的预算。超预算的模式会被原样使用,字面花括号匹配不到任何文件。
踩坑预警
glob 语法把 `[` 当作方括号表达式(如 `[abc]`)的开始。一个包含无法被读作方括号表达式的 `[` 的模式,比如 `photos [2024/**`,是无效的:它匹配不到任何文件,但规则里其他模式照常工作。
要匹配文件名里的字面 `[`,转义成 `photos \[2024/**`。
v2.1.207 之前,一个无效模式会让 Read 工具对这条规则求值过的每个文件都失败,而不是仅仅匹配不到。所以老版本上遇到「读文件莫名报错」,检查一下 rules 里的 glob。
`.claude/rules/` 支持符号链接,可以维护一份共享规则链进多个项目:
```bash
ln -s ~/shared-claude-rules .claude/rules/shared
ln -s ~/company-standards/security.md .claude/rules/security.md
```
循环符号链接会被检测并妥善处理。
用户级规则放 `~/.claude/rules/`,在这台机器的每个项目都生效。用户级规则先于项目规则加载,所以项目规则优先级更高。
## 怎么写才管用
[Section titled “怎么写才管用”](#怎么写才管用)
三个维度:
**大小**:目标每个 CLAUDE.md 200 行以内。更长的文件消耗更多上下文,并且降低遵守度。
**结构**:用 markdown 标题和列表分组。Claude 扫描结构的方式和人读一样,组织好的章节比密集段落更容易跟随。
**具体性**:写具体到可验证的指令。
* 「用 2 空格缩进」而不是「正确格式化代码」
* 「提交前跑 `npm test`」而不是「测试你的改动」
* 「API handler 放 `src/api/handlers/`」而不是「保持文件组织」
什么时候该往里加内容——这个判断标准比写法更重要:
* Claude 第二次犯同一个错
* code review 抓到一个 Claude 本该知道的项目约定
* 你把上个会话打过的同一句纠正又打了一遍
* 一个新同事需要同样的上下文才能开工
什么时候不该加:如果一条内容是多步流程,或者只对代码库的某一部分有意义,它该去 skill 或路径限定的 rule,不该占用每个会话的上下文。
## 内容分流
[Section titled “内容分流”](#内容分流)
| 内容类型 | 放哪 |
| ------------------------ | -------------------------- |
| 每个会话都需要的事实(构建命令、约定、项目布局) | CLAUDE.md |
| 只对某些文件有意义的规则 | `.claude/rules/` 配 `paths` |
| 多步流程(PR review、数据库迁移) | skill |
| 必须在特定时机发生的动作 | hook |
| 需要在系统提示层面的指令 | `--append-system-prompt` |
最后一项要每次调用都传,所以更适合脚本和自动化,不适合交互式使用。
## 压缩之后什么还在
[Section titled “压缩之后什么还在”](#压缩之后什么还在)
这是一个高频困惑:`/compact` 之后某条指令好像失效了。
规则是:
* **项目根的 CLAUDE.md 活过压缩**。压缩后 Claude 从磁盘重读并重新注入。
* **子目录里的嵌套 CLAUDE.md 不会自动重新注入**。它们在 Claude 下一次读取那个子目录里的文件时重新加载。
所以压缩后消失的指令,要么只在对话里给过(没写进文件),要么在一个还没重新加载的嵌套 CLAUDE.md 里。
对话里给的临时指令想让它持久,写进 CLAUDE.md。
## 排查「Claude 没听 CLAUDE.md」
[Section titled “排查「Claude 没听 CLAUDE.md」”](#排查claude-没听-claudemd)
按顺序:
1. 跑 `/context`,看 `Memory files` 下的列表确认文件真的加载了。不在列表里,Claude 就看不到。用 `/memory` 打开编辑。
2. 确认这个 CLAUDE.md 在会话会加载的位置上。
3. 让指令更具体。
4. 找跨文件的冲突指令。两个文件对同一行为给了不同指导,Claude 可能任意选一个。
调试 path-scoped rules 和子目录里延迟加载的文件,用 `InstructionsLoaded` hook 记录到底加载了哪些指令文件、什么时候、为什么。这比反复猜快得多。
`/doctor` 会对已提交的 CLAUDE.md 提出精简建议:它删掉 Claude 能从代码库自行推断的内容(目录布局、依赖列表、架构概览),保留陷阱、理由、以及与工具默认行为不同的约定。这个精简检查需要 v2.1.206 或更高版本。
这个取舍逻辑值得记住:**能推断的不写,不能推断的才写**。目录结构 Claude 一个 `ls` 就知道了,写进 CLAUDE.md 是纯浪费;「这个模块看起来该用 A 方案但因为历史原因必须用 B」是它推不出来的,必须写。
## 自动记忆:Claude 自己写的那部分
[Section titled “自动记忆:Claude 自己写的那部分”](#自动记忆claude-自己写的那部分)
除了你写的 CLAUDE.md,还有一套 Claude 自己维护的记忆。
| | CLAUDE.md | 自动记忆 |
| --- | ------------ | ----------------- |
| 谁写 | 你 | Claude |
| 内容 | 指令和规则 | 学到的东西和模式 |
| 作用域 | 项目 / 用户 / 组织 | 每仓库,worktree 之间共享 |
| 加载 | 每个会话全量 | 每个会话前 200 行或 25KB |
存储位置是 `~/.claude/projects//memory/`,`` 从 git 仓库派生,所以同一个仓库的所有 worktree 和子目录共享一个目录。
目录结构:
```plaintext
~/.claude/projects//memory/
├── MEMORY.md # 简明索引,每个会话加载
├── debugging.md # 详细笔记
└── api-conventions.md
```
`MEMORY.md` 只有前 200 行或前 25KB(先到者为准)在每个会话开始时加载,超出部分不加载。所以它被设计成一个索引,详细笔记放在单独的主题文件里,Claude 需要时用文件工具按需读。
Claude 写完 `MEMORY.md` 后 Claude Code 会量一次:接近上限时提醒 Claude 精简,超过上限时写入仍然成功但返回一个错误告诉 Claude 重写索引——因为超出部分在下次加载时会被丢掉。
量的是**会加载的内容**:YAML frontmatter 和块级 HTML 注释在加载前被剥掉,不计入上限。
这个上限只对 `MEMORY.md` 生效。CLAUDE.md 无论多长都全量加载,只是越短遵守度越好。
自动记忆是机器本地的,不跨机器同步。主对话的自动记忆不会加载进 subagent(fork 是例外,它继承父对话)。
关掉:
```json
{ "autoMemoryEnabled": false }
```
或环境变量 `CLAUDE_CODE_DISABLE_AUTO_MEMORY=1`。用 `/memory` 可以浏览 Claude 存了什么——都是纯 markdown,随时可以编辑或删除。
## 变式练习
[Section titled “变式练习”](#变式练习)
* 跑 `/context` 看 CLAUDE.md 在上下文里占多少。如果占比让你意外,用 `/doctor` 的精简建议砍一轮。
* 把 CLAUDE.md 里最长的那个「流程」章节搬成 skill,再跑 `/context` 对比。这个对比能让「200 行」这个建议从数字变成体感。
* 给一个只对某个目录有意义的规则加 `paths` frontmatter,然后分别在匹配和不匹配的文件上工作,用 `InstructionsLoaded` hook 确认它真的只在该加载时加载。
## 还没确认的点
[Section titled “还没确认的点”](#还没确认的点)
* `claudeMdExcludes` 对嵌套 rules 目录的匹配细节。文档给了示例,但「排除一个目录」和「排除目录下的具体文件」在行为上是否有区别没有核实。
* 自动记忆的写入判定。文档说 Claude 根据「这条信息在未来对话里是否有用」决定要不要存,具体的判定过程不可观测。
* `/doctor` 精简建议的具体判定规则。文档描述了它保留什么删什么,但没有给出可预测的规则。
* 压缩后重新注入的具体时机。「项目根 CLAUDE.md 活过压缩」确认了,但重新注入是紧接压缩之后还是下一轮请求时,未见说明。
## 参考
[Section titled “参考”](#参考)
* [How Claude remembers your project - Claude Code Docs](https://code.claude.com/docs/en/memory)
* [Extend Claude with skills - Claude Code Docs](https://code.claude.com/docs/en/skills)
# Claude Code /goal 命令:给一个完成条件,让它自己跑到达标
> /goal 用完成条件替代固定时间间隔,每轮结束后由小模型评估是否达标;没有 7 天过期、没有固定等待间隔,配合 auto mode 能在当前会话里连续跑很久。
适用范围
适用工具:Claude Code CLI,需要 v2.1.139 及以上版本;桌面客户端与 Remote Control 模式同样支持。最后核对日期:2026-07-30。前置假设:已安装 Claude Code,工作区已通过 trust dialog 信任。
## 结论
[Section titled “结论”](#结论)
`/goal <完成条件>` 设定后立即用这个条件启动一轮;每轮结束后,一个独立的小模型(Claude API 上默认 Haiku)判断条件是否满足,不满足就自动继续下一轮,满足就清除 goal 并标记为「已达成」。它和 `/loop` 最大的区别:`/loop` 靠固定时间间隔驱动、有 7 天强制过期;`/goal` 靠「条件是否达标」驱动,官方文档里没有设默认的轮数或时长上限——这也是为什么它能被随手挂着跑一两天:只要终端进程别断、条件别被判定为满足,它就会一轮接一轮跑下去。真正限制它能跑多久的不是某个内置计时器,而是终端/进程要不要保持存活、以及要不要人工确认工具调用。
## 怎么设一个有效的条件
[Section titled “怎么设一个有效的条件”](#怎么设一个有效的条件)
* **命令格式**:`/goal <条件描述>`,比如 `/goal all tests in test/auth pass and the lint step is clean`
* 已有活跃 goal 时,新设定的会**直接替换**旧的,不是叠加。
* **长度上限**:条件描述最多 4000 字符。
* **有效条件的三要素**:
1. 可衡量的终态(测试结果、构建退出码、文件数量……)
2. 明确的验证方式(比如「`npm test` exits 0」)
3. 必须保持不变的约束(比如「不修改其他测试文件」)
* 想限制轮数或时长,必须**主动写进条件里**,比如「`... or stop after 20 turns`」——不写就没有上限。
## 每轮怎么评估
[Section titled “每轮怎么评估”](#每轮怎么评估)
| 项目 | 说明 |
| ------- | ------------------------------------------------------------------------------ |
| 触发时机 | 每轮(turn)结束后 |
| 评估模型 | Claude API 上默认 Haiku;第三方 provider 上用该平台配置的小型快速模型 |
| 自定义评估模型 | 环境变量 `ANTHROPIC_DEFAULT_HAIKU_MODEL`(注意:这个变量是全局生效的,还会影响对话摘要等其他后台功能,不止 `/goal`) |
| 评估器权限 | **不能调用工具**,只能基于对话里已经展示的内容判断,不会自己去跑命令或读文件 |
| 结果处理 | No:继续下一轮,评估理由作为下一轮参考;Yes:清除 goal,标记「已达成」 |
| 评估花费 | 计入小型快速模型用量,相对主模型花费通常可忽略 |
## 和 /loop、Stop Hook 的关系
[Section titled “和 /loop、Stop Hook 的关系”](#和-loopstop-hook-的关系)
| 对比维度 | `/goal` | `/loop` |
| ------- | -------------- | ---------------------- |
| 下一轮启动条件 | 上一轮结束后立即判断 | 时间间隔到达后 |
| 停止条件 | 评估模型确认条件已满足 | 用户手动停止,或 Claude 自行判断完成 |
| 判定者 | 独立的评估模型,每轮判断一次 | 执行任务的模型自己判断 |
| 适用场景 | 有明确可验证终态的任务 | 需要按固定间隔重复执行的任务 |
还有一种更底层的方式是 **Stop Hook**:触发时机同 `/goal`(每轮结束后),但停止判定由你自己的脚本或提示词决定(可以是确定性检查,也可以是模型评估),配置在 settings 文件里,作用范围是**所有会话**;`/goal` 只作用于**当前会话**,是对 Stop Hook 的一层快捷封装。三者不冲突:auto mode 解除单次工具调用的确认,`/goal`/Stop Hook 解除单轮之间的确认,是互补关系。
## 三种运行模式
[Section titled “三种运行模式”](#三种运行模式)
1. **交互模式**:正常在 CLI 里输入 `/goal <条件>`。
2. **非交互模式(`-p`)**:
```plaintext
claude -p "/goal CHANGELOG.md has an entry for every PR merged this week"
```
默认文本输出下,条件未达成前不会打印任何内容,看起来像卡住了,建议加 `--output-format stream-json --verbose` 实时查看每条消息。用 `Ctrl+C` 中断未完成的 goal。
3. **Remote Control 与桌面客户端**:同样支持 `/goal`。
想真的挂着跑很久,还要打开 auto mode
`/goal` **不改变工具调用的权限设置**。默认权限模式下,Claude 遇到未被允许的工具调用(比如跑测试命令)仍会停下来问你。想无人值守地连续跑,需要搭配 auto mode 一起用,否则条件写得再好,实际也会卡在权限确认那一步。
## 会话恢复与清除行为
[Section titled “会话恢复与清除行为”](#会话恢复与清除行为)
* 用 `--resume` 或 `--continue` 恢复会话时,若结束时 goal 仍活跃,**条件会被保留**,但轮数计数、计时器、token 花费基线都会**重置**。
* 已达成或已被清除的 goal **不会**被恢复。
* `/clear` 开新对话会**移除**当前活跃的 goal。
## 管理命令
[Section titled “管理命令”](#管理命令)
| 操作 | 命令 | 说明 |
| ---- | ------------- | ----------------------------------------- |
| 设定 | `/goal <条件>` | 已有活跃 goal 会被替换 |
| 查看状态 | `/goal`(不带参数) | 显示条件内容、已运行时长、已评估轮数、当前 token 消耗、评估器最近一次的理由 |
| 清除 | `/goal clear` | 同义词:`stop`、`off`、`reset`、`none`、`cancel` |
官方文档里没有「暂停」命令,只有设定、查看状态、清除三种操作。
## 前置条件
[Section titled “前置条件”](#前置条件)
* 版本需 `Claude Code v2.1.139` 及以上。
* 工作区必须已通过 trust dialog 信任(评估器依赖 hooks 系统)。
* 以下情况会导致 `/goal` **不可用**(会提示原因,不是静默失败):任意设置层级启用了 `disableAllHooks`;managed settings 里设了 `allowManagedHooksOnly`。
## 还没确认的点
[Section titled “还没确认的点”](#还没确认的点)
* 官方文档没有直接说明上下文窗口/自动压缩对评估准确性的具体影响——评估器依赖「对话里已展示的内容」,如果上下文被压缩,理论上可能影响判断,但这是推测,不是文档原文,没有做过本机复现。
* 「终端/进程必须保持存活」这条是从非交互模式的行为描述里推出来的,文档没有直接下这个结论,也没有本机验证过关掉终端后 goal 是否真的会中断。
* 没有做过真正挂 48 小时以上的本机复现,本篇内容全部来自官方文档,实际长跑表现(比如评估器是否会因为长对话而判断变差)留待后续验证。
## 参考
[Section titled “参考”](#参考)
* [Keep Claude working toward a goal - Claude Code Docs](https://code.claude.com/docs/en/goal)
# 让 Claude Code 跑很久:/loop、/goal、Routines 和 Desktop scheduled tasks 该选哪个
> 跑很久不一定要跳出会话——/goal 没有 /loop 的 7 天过期和固定等待间隔,配合 auto mode 就能连续跑很多轮。这篇讲清楚四种机制的边界和怎么选。
适用范围
适用工具:Claude Code CLI 与桌面客户端,Routines 相关能力标注为研究预览(research preview)阶段,可能随版本调整;`/goal` 需要 v2.1.139 及以上版本。最后核对日期:2026-07-30。
## 结论
[Section titled “结论”](#结论)
跑很久不一定要跳出当前会话。如果任务有一个能被验证的完成条件,`/goal` 配合 auto mode 就能在当前会话里连续跑很多轮——它没有 `/loop` 那样的 7 天强制过期,也不用像 `/loop` 一样按固定间隔等待,条件没达标就直接开始下一轮,这就是为什么它能被随手挂着跑一两天。但它仍然是「会话范围」的机制:终端或客户端进程要保持存活,重启会中断(`--resume` 能找回条件,但轮次和计时会重置)。真正需要「合上电脑也在跑」,或者要按固定时刻、外部事件反复触发的,才需要 Routines(云端,研究预览)或 Desktop scheduled tasks(本机,跨重启持久)。四种机制解决的是不同的问题,不是谁比谁更强的升级关系。
## 四种机制怎么选
[Section titled “四种机制怎么选”](#四种机制怎么选)
| | `/loop`(固定间隔) | `/goal`(完成条件) | Desktop scheduled tasks | Routines |
| -------- | ---------------- | -------------------------------- | ----------------------- | --------------------- |
| 运行位置 | 当前会话进程内 | 当前会话进程内 | 本机,Claude 桌面客户端 | 云端 |
| 终端/客户端要求 | 必须保持打开 | 必须保持打开 | 客户端需在运行 | 不需要,可关闭设备 |
| 跨重启持久 | 不支持,重启即丢 | 不支持(条件可用 `--resume` 找回,但轮次/计时重置) | 支持 | 支持(云端天然持久) |
| 触发方式 | 固定/自主间隔,7 天后过期 | 每轮结束自动评估,不满足就继续,无固定间隔和默认上限 | 定时 | 定时、Webhook、GitHub 事件等 |
| 本机文件访问 | 有 | 有 | 有 | 无(跑在云端沙箱里) |
| 适用场景 | 短时会话内轮询、调试期的重复检查 | 有明确可验证完成条件,想让当前会话连续跑到达标 | 需要本机环境、但要求重启不丢的日常任务 | 真正长期、脱离设备、接外部事件的自动化 |
## 什么时候选哪个
[Section titled “什么时候选哪个”](#什么时候选哪个)
* 只是在当前调试会话里想「每隔几分钟看一眼」,用 `/loop`,见《[loop 命令怎么用](../loop-agent/)》。
* 任务有明确的、可验证的完成条件(比如「直到测试全绿」「直到 CHANGELOG 补全」),想让 Claude 自己反复迭代直到达标,用 `/goal`,见《[/goal 命令怎么用](../goal-command/)》。
* 需要读写本机文件、依赖本机已装的工具链,但又不想每次重启电脑后手动重新创建,用 Desktop scheduled tasks。
* 需要真正的「设备关了也在跑」,或者要接 GitHub 事件、Webhook 之类外部触发,用 Routines。
## /goal 为什么能被随手挂着跑一两天
[Section titled “/goal 为什么能被随手挂着跑一两天”](#goal-为什么能被随手挂着跑一两天)
`/loop` 的 7 天过期和固定等待间隔,是专门为了防止「被遗忘的循环任务无限期占用资源」而设的限制;`/goal` 没有对应的限制——条件不满足,它就直接开始下一轮,官方文档里没有默认的轮数或时长上限。所以只要满足两个前提,`/goal` 确实可以连续跑很久:
1. 终端/客户端进程别断——这是「会话范围」机制共同的硬要求,`/loop` 也一样;
2. 打开 auto mode,否则默认权限模式下每次未被允许的工具调用还是会停下来等确认。
想真的挂一两天不管,最好在条件里主动写清楚兜底(比如「或者跑满 200 轮就停」),避免条件写得太模糊、评估器一直判 no 而空转浪费。
## 用 Routines:`/schedule`
[Section titled “用 Routines:/schedule”](#用-routinesschedule)
Routines 的入口是 `/schedule`,用自然语言描述即可:
```plaintext
/schedule daily code review of open PRs at 9am
/schedule list
/schedule pause
/schedule run
```
创建方式除了对话里的 `/schedule`,还可以在 Claude Code 网页端的 Routines 页面配置,或者调用 API 触发一次性运行(用于接入外部系统,比如 CI 流水线在特定阶段触发一次 Routine)。触发条件不止定时,还支持 GitHub 事件(比如「有新 PR 打开时跑一次审查」)。
每次触发的 Routine 是一次独立的新会话,不会复用上一次触发时的上下文,这点和 `/loop` 相反:`/loop` 是同一个会话反复追加消息,上下文会一直涨;Routine 每次都是干净的开始,没有「记忆延续」,也就不存在上下文膨胀的问题,但也意味着它记不住上一次跑的中间状态。
## 怎么确认任务真的在跑,而不是「看起来正常」
[Section titled “怎么确认任务真的在跑,而不是「看起来正常」”](#怎么确认任务真的在跑而不是看起来正常)
踩坑预警
Routines 运行列表里的绿色状态图标,只代表这次会话正常启动、正常退出,不代表任务真正达成了目的。比如一次「审查 PR」的 Routine,会话本身跑完没有报错,但可能只是打开了 PR 页面看了一眼就退出,没有真的留下审查意见。确认任务效果要点进具体的运行记录,看实际产生的 diff、PR 评论或网络请求,不能只看状态图标是不是绿的。
## 配额限制
[Section titled “配额限制”](#配额限制)
Routines 的运行计入账户的用量额度,按账户每天有运行次数上限;手动触发的一次性运行不计入这个每日上限。如果是团队账户,管理员可以在组织层面整体禁用 Routines 功能。
## 长跑任务的上下文策略
[Section titled “长跑任务的上下文策略”](#长跑任务的上下文策略)
如果场景需要「同一个会话持续跑很久」而不是「每次触发都是新会话」(比如 `/goal`、`/loop`,或者 Desktop scheduled tasks 绑定同一个会话反复触发),上下文膨胀的问题都是一样的:消息会一直累积,最终触发自动压缩。跑得越久越容易撞到这个问题,提前确认压缩阈值符合预期,比等到长跑任务中途报错才发现划算,具体配置见《[自动压缩阈值配置](../auto-compact-window/)》。
## 还没确认的点
[Section titled “还没确认的点”](#还没确认的点)
* Desktop scheduled tasks 的具体创建入口、触发日志的查看方式,官方文档在独立页面描述,这篇没有展开逐条核实,后续需要补测。
* Routines 目前标注为研究预览,配额规则、支持的触发事件类型可能会随版本调整,写这篇时看到的细节不保证长期有效。
* `/goal` 挂着连续跑一两天时,评估器的判断准确性会不会随对话变长而下降,没有做过本机复现,详见《[/goal 命令怎么用](../goal-command/)》里的未确认清单。
## 参考
[Section titled “参考”](#参考)
* [Keep Claude working toward a goal - Claude Code Docs](https://code.claude.com/docs/en/goal)
* [Automate Claude Code with Routines - Claude Code Docs](https://code.claude.com/docs/zh-CN/routines)
* [Run prompts on a schedule - Claude Code Docs](https://code.claude.com/docs/en/scheduled-tasks)
# Claude Code 循环任务:/loop 命令怎么用,边界在哪
> /loop 内置技能的三种写法、时间参数格式、会话范围与 7 天过期规则,以及规划里提到的别名 /proactive 是否真的存在。
适用范围
适用工具:Claude Code CLI,涉及行为在 v2.1.196 附近的版本说明中被提及。最后核对日期:2026-07-30。前置假设:已安装 Claude Code,能开一个交互会话。
## 结论
[Section titled “结论”](#结论)
`/loop` 是一个内置技能(bundled skill),最简单的写法是 `/loop <间隔> <提示词>`,间隔和提示词都可以省略。任务绑定在当前会话,开新会话会清空所有循环任务,循环任务本身在创建后 7 天自动过期。官方文档里没有出现「`/proactive`」这个词,也没有把它列为 `/loop` 的别名,写这篇之前一直当作既定事实的说法没有找到依据。
如果任务不是「每隔多久检查一次」,而是有一个明确的完成条件(比如「直到测试全绿」),更合适的机制是 `/goal`:它没有固定间隔和 7 天过期的限制,条件不满足就直接开始下一轮,见《[/goal 命令怎么用](../goal-command/)》。
## 三种写法
[Section titled “三种写法”](#三种写法)
| 输入方式 | 示例 | 行为 |
| --------- | --------------------------- | ------------------------- |
| 间隔 + 提示词 | `/loop 5m check the deploy` | 按固定间隔运行指定提示词 |
| 仅提示词 | `/loop check the deploy` | 按 Claude 自主选择的间隔运行 |
| 仅间隔或什么都不填 | `/loop` 或 `/loop 15m` | 运行内置维护提示词,或自定义的 `loop.md` |
提示词位置可以传一个技能,等于把某个斜杠命令定时化:
```plaintext
/loop 20m /review-pr 1234
```
## 时间参数怎么写
[Section titled “时间参数怎么写”](#时间参数怎么写)
裸词形式放在提示词前(`30m ...`),或者当作短语放在提示词后(`... every 2 hours`)。支持的单位:
* `s`:秒,会向上取整到最近的分钟,因为底层 cron 的最小粒度是 1 分钟
* `m`:分钟
* `h`:小时
* `d`:天
不能整除为标准 cron 步长的间隔(比如 `7m`、`90m`)会被四舍五入到最近的可用值,Claude 会告知实际选定的间隔。底层用标准 5 字段 cron 表达式(分钟 小时 日 月 星期)实现,支持 `*`、单值、步长 `*/15`、范围 `1-5`、逗号列表 `1,15,30`,不支持 `L`/`W`/`?` 这类扩展语法,也不支持月份或星期的英文别名(如 `MON`、`JAN`)。
一次性提醒不需要 `/loop`,直接用自然语言描述时间即可:
```plaintext
remind me at 3pm to push the release branch
in 45 minutes, check whether the integration tests passed
```
## 循环任务的边界,决定了它会不会「越跑越偏」
[Section titled “循环任务的边界,决定了它会不会「越跑越偏」”](#循环任务的边界决定了它会不会越跑越偏)
* **会话范围**:任务存在于当前对话里,开新对话会清空所有任务。用 `--resume` 或 `--continue` 恢复会话时,7 天内创建且尚未过期的循环任务会被找回。
* **触发时机**:调度器每秒检查一次到期任务,以低优先级排队。任务只在会话的对话轮次之间触发,不会打断 Claude 正在生成的回复;如果 Claude 正忙,任务会等当前轮次结束才触发。所有时间按本地时区解释。
* **抖动(jitter)**:为避免多个会话同时打 API,循环任务最多延后 30 分钟触发(间隔小于 1 小时时,最多延后半个间隔);整点/半点的一次性任务可能提前最多 90 秒触发。偏移量由任务 ID 派生,同一任务每次偏移相同。
* **无补跑**:调度时间到达时 Claude 正忙,错过的间隔不会补上,只会在 Claude 空闲时触发一次。
* **7 天到期**:循环任务创建后 7 天自动过期,到期时触发最后一次后自我删除,用来限制被遗忘的循环任务无限期运行。
* **停止循环**:等待下一次迭代期间按 `Esc` 可以清除待触发的循环。在「自主选择间隔」模式下,Claude 也能自行结束循环;如果某次迭代既没有重新调度也没有停止,系统会在约 20 分钟后安排一次兜底触发,那次仍未重新调度的话循环就此结束。
踩坑预警
定时触发只会运行 Claude「被允许自主调用」的技能,以下几类会以纯文本形式传给 Claude,而不会真正执行:内置命令(`/permissions`、`/model`、`/clear`);标记了 `disable-model-invocation: true` 的技能,这里包括内置的 `/verify` 和 `/code-review`;被 `skillOverrides` 或 `Skill` deny 规则屏蔽的技能;MCP prompts,比如 `/mcp__github__list_prs`。如果循环任务的提示词恰好是这几类,实际效果是 Claude 把命令原文当普通文字读了一遍,没有执行。
## 循环与上下文膨胀的关系
[Section titled “循环与上下文膨胀的关系”](#循环与上下文膨胀的关系)
循环任务每次触发都会往同一个会话里追加消息,会话的上下文只会越涨越多,不会自动清空。7 天过期限制的是循环任务本身的生命周期,不限制上下文大小,这是两个独立的约束。上下文涨到一定程度会触发自动压缩(auto-compact),阈值怎么配、怎么确认压缩真的发生,见另一篇《[自动压缩阈值配置](../auto-compact-window/)》。跑长循环之前先确认压缩阈值符合预期,比等到上下文报错才发现更省事。
## 怎么确认循环真的在跑
[Section titled “怎么确认循环真的在跑”](#怎么确认循环真的在跑)
底层管理循环任务的是 `CronCreate`、`CronList`、`CronDelete` 三个工具,这些是 Claude 调用的内部工具,不是能直接敲的斜杠命令。确认任务状态的办法是直接用自然语言问 Claude,比如「现在有哪些循环任务在跑」,让它自己调用 `CronList` 汇报;或者等到预期的触发时间点,观察会话里是否出现了对应的输出。
## 关掉整个调度器
[Section titled “关掉整个调度器”](#关掉整个调度器)
设置环境变量 `CLAUDE_CODE_DISABLE_CRON=1` 可以完全禁用调度功能,`/loop` 以及相关的 cron 工具会变为不可用,已经调度的任务也会停止触发。
## 还没确认的点
[Section titled “还没确认的点”](#还没确认的点)
* `/proactive` 这个别名在当前查到的官方文档里没有出现,不代表所有历史版本都没有过。如果在别的渠道看到这个说法,建议用 `/help` 或查当前版本号自行核对。
* `ScheduleWakeup` 工具(用于「自主选择间隔」模式下自行结束循环)的细节只来自公开文档描述,没有做过本机复现。
* 单个会话最多可持有 50 个并发调度任务,这个数字没有本机验证过是否会随版本调整。
## 参考
[Section titled “参考”](#参考)
* [Run prompts on a schedule - Claude Code Docs](https://code.claude.com/docs/en/scheduled-tasks)
# Claude Code skills:SKILL.md 全字段、谁能调用它、以及为什么内容进了上下文就不走了
> skills 的四层存放位置与覆盖顺序、frontmatter 全部字段含义、disable-model-invocation 与 user-invocable 的组合语义、!`command` 动态上下文注入、以及 skill 内容在压缩后的重新附加预算。
适用范围
适用工具:Claude Code CLI(v2.1.x 系列)。最后核对日期:2026-07-30。核实来源:官方 skills 文档。前置假设:读过[CLAUDE.md 上下文工程](/agents/claude-md-context/),知道为什么要把流程从 CLAUDE.md 里搬出来。
## 结论
[Section titled “结论”](#结论)
什么时候该写 skill:**你在反复把同一段指令、清单或多步流程粘进对话**,或者 **CLAUDE.md 里某一节已经从「事实」长成了「流程」**。
和 CLAUDE.md 内容不同,skill 的正文只在被用到时加载,所以长参考资料在你需要它之前几乎不花钱。
但有一个关键限制常被忽略:**skill 内容一旦进入上下文,就留到会话结束**。Claude Code 不会在后续轮次重读 skill 文件。所以该在整个任务期间生效的指导要写成「常驻指令」的语气,不是「一次性步骤」。
自定义命令已经合并进 skills。`.claude/commands/deploy.md` 和 `.claude/skills/deploy/SKILL.md` 都产生 `/deploy`,行为一致。已有的 `.claude/commands/` 文件继续可用,skills 多了三样东西:放辅助文件的目录、控制调用权的 frontmatter、以及被 Claude 自动加载的能力。
## 存放位置与覆盖顺序
[Section titled “存放位置与覆盖顺序”](#存放位置与覆盖顺序)
| 位置 | 路径 | 作用范围 |
| -- | ---------------------------------- | ------ |
| 企业 | 见 managed 设置 | 组织全体 |
| 个人 | `~/.claude/skills//SKILL.md` | 你的所有项目 |
| 项目 | `.claude/skills//SKILL.md` | 仅这个项目 |
| 插件 | `/skills//SKILL.md` | 插件启用处 |
同名时的覆盖顺序:企业 > 个人 > 项目。任意一层的 skill 也会覆盖同名的内置 skill——项目 `.claude/skills/` 里的 `code-review` 会替换内置的 `/code-review`。
插件 skill 用 `plugin-name:skill-name` 命名空间,不会和其他层冲突。
skill 和同名 command 同时存在时,skill 优先。
### 子目录里的 skill 何时可用
[Section titled “子目录里的 skill 何时可用”](#子目录里的-skill-何时可用)
项目 skill 从你启动 Claude Code 的目录以及**每一级父目录**直到仓库根加载。在子目录里启动仍然能拿到根上定义的 skill。
启动目录**下面**的嵌套 `.claude/skills/` 不在启动时加载。它们在 Claude 第一次读取或编辑那个子目录里的文件时加载,之后整个会话都可用。在那之前它们不出现在自动补全里,也不能按名字调用。
这是 monorepo 里的一个实际影响:`packages/frontend/.claude/skills/` 里的 skill,在 Claude 碰过那个目录下的文件之前是「不存在」的。
嵌套 skill 和别的 skill 同名时:
* 嵌套的那个以目录限定名出现,如 `apps/web:deploy`
* 它的描述会说明它适用于哪个目录
* Claude 挑选匹配它正在处理的文件的那个变体
敲 `/deploy` 运行项目根的那个。敲 `/apps/web:deploy` 显式运行嵌套变体。
## frontmatter 全字段
[Section titled “frontmatter 全字段”](#frontmatter-全字段)
所有字段都是可选的,只有 `description` 是推荐的——Claude 靠它判断什么时候用这个 skill。
布尔字段接受 `yes`、`no`、`on`、`off`、`1`、`0`(任意大小写),以及 `true`、`false`。v2.1.218 之前只认后两个。
| 字段 | 说明 |
| -------------------------- | ------------------------------------------------------- |
| `name` | 列表里显示的名字。默认取目录名。个人和项目 skill 里它**只影响显示**,命令名仍来自目录名 |
| `description` | 做什么、何时用。省略时取 markdown 正文第一段 |
| `when_to_use` | 补充触发场景,追加在 description 后面 |
| `argument-hint` | 自动补全时显示的参数提示,如 `[issue-number]` |
| `arguments` | 命名位置参数,用于 `$name` 替换。名字按顺序映射到参数位置 |
| `disable-model-invocation` | `true` 时只有你能调用 |
| `user-invocable` | `false` 时从 `/` 菜单隐藏,只有 Claude 能调用 |
| `allowed-tools` | 调用这个 skill 的那一轮里可以不询问就使用的工具 |
| `disallowed-tools` | skill 活跃期间从 Claude 可用工具池里移除的工具 |
| `model` | 这个 skill 活跃时用的模型。覆盖只在当前轮次生效,不写进设置 |
| `effort` | 这个 skill 活跃时的 effort level |
| `context` | 设为 `fork` 在 fork 出的 subagent 上下文里运行 |
| `agent` | `context: fork` 时用哪种 subagent 类型 |
| `background` | 仅在 `context: fork` 时有效。设 `false` 则在调用它的那一轮等结果。默认 `true` |
| `hooks` | 限定在这个 skill 生命周期内的 hook |
| `paths` | glob 模式,限制何时自动激活这个 skill |
| `shell` | `!`command“ 块用哪个 shell,`bash`(默认)或 `powershell` |
`description` 和 `when_to_use` 的合并文本在 skill 列表里被截断到 1536 字符,所以**把最关键的用例写在最前面**。
## 两个方向的调用权
[Section titled “两个方向的调用权”](#两个方向的调用权)
默认你和 Claude 都能调用任意 skill。两个字段各限制一个方向:
| frontmatter | 你能调用 | Claude 能调用 | 何时加载进上下文 |
| -------------------------------- | ---- | ---------- | ---------------- |
| (默认) | 是 | 是 | 描述常驻上下文,调用时加载全文 |
| `disable-model-invocation: true` | 是 | 否 | 描述不进上下文,你调用时加载全文 |
| `user-invocable: false` | 否 | 是 | 描述常驻上下文,调用时加载全文 |
`disable-model-invocation: true` 用于有副作用或需要你控制时机的流程:`/commit`、`/deploy`、`/send-slack-message`。你不希望 Claude 因为「代码看起来准备好了」就自己去部署。
`user-invocable: false` 用于背景知识——一个 `legacy-system-context` skill 解释老系统怎么工作,Claude 该在相关时知道,但 `/legacy-system-context` 对用户来说不是一个有意义的动作。
踩坑预警
`user-invocable` 只控制菜单可见性,**不控制 Skill 工具的访问**。要阻止程序化调用必须用 `disable-model-invocation: true`。
这两个字段名字对称,语义不对称。想「让 Claude 别碰这个」时选错字段是常见错误。
`disable-model-invocation: true` 还有两个附带效果:阻止这个 skill 被预加载进 subagent;v2.1.196 起,也阻止它在[定时任务](/agents/loop-agent/)以它为提示词触发时运行。
## 内容生命周期:为什么「加载了就不走」很重要
[Section titled “内容生命周期:为什么「加载了就不走」很重要”](#内容生命周期为什么加载了就不走很重要)
你或 Claude 调用一个 skill 时,渲染后的 SKILL.md 内容作为一条消息进入对话,然后**留在那里直到会话结束**。
三个推论:
**Claude Code 不在后续轮次重读 skill 文件**。所以该在整个任务期间适用的指导写成常驻指令,不要写成一次性步骤。
**重复调用不会重复堆叠**。Claude 重新调用一个 skill 时,如果渲染内容和上下文里已有的副本完全相同,Claude Code 只加一条「已加载」的短提示,不加第二份内容。渲染内容不同时(参数变了,或动态上下文命令产出了新输出)才追加完整内容。v2.1.202 之前每次重新调用都追加一份完整副本。
**权限授予的生命周期和内容不同**。`allowed-tools` 的授予在你发送下一条消息时就清除了,即使 skill 内容还在上下文里。
### 压缩之后
[Section titled “压缩之后”](#压缩之后)
自动压缩会把已调用的 skill 带过来,但有 token 预算:
* 每个 skill 保留最近一次调用的**前 5000 token**
* 所有重新附加的 skill 共享 **25000 token** 的总预算
* 从最近调用的 skill 开始填这个预算
所以一个会话里调用了很多 skill 的话,早期的可能在压缩后被完全丢掉。
如果一个 skill 在第一次回复之后好像不再影响行为了,内容通常还在,是模型选了其他工具或路径。强化 description 和指令让模型继续偏向它,或者用 hook 做确定性的强制。skill 很大、或者之后又调用了好几个别的,压缩后重新调用一次恢复完整内容。
## 动态上下文注入
[Section titled “动态上下文注入”](#动态上下文注入)
`` !`` `` 语法在 skill 内容送给 Claude **之前**执行 shell 命令,输出替换占位符。
```markdown
---
name: summarize-changes
description: Summarizes uncommitted changes and flags anything risky. Use when the user asks what changed, wants a commit message, or asks to review their diff.
---
## Current changes
!`git diff HEAD`
## Instructions
Summarize the changes above in two or three bullet points, then list any risks.
```
这是**预处理**,不是 Claude 执行的东西。Claude 只看到最终结果。
这个机制的价值在于「让 skill 的输出扎根于真实状态」。上面这个 skill 拿到的是当前工作树的真实 diff,不是 Claude 从打开的文件里猜的。
规则细节:
* 替换只对原始文件跑一遍。命令输出作为纯文本插入,**不会**被再次扫描寻找新的占位符,所以一个命令无法产出一个占位符让后续轮次展开。
* 内联形式只在 `!` 出现在行首或紧跟空白时被识别。`!` 跟在其他字符后面(如 `KEY=!`cmd“)时占位符保持字面文本,命令不运行。
* 多行命令用 ` ```! ` 开头的围栏代码块。
组织侧可以关掉:`"disableSkillShellExecution": true`。命令会被替换成 `[shell command execution disabled by policy]`。内置和 managed skill 不受影响。
## 参数替换
[Section titled “参数替换”](#参数替换)
| 变量 | 说明 |
| ----------------------- | ------------------------------------------- |
| `$ARGUMENTS` | 全部参数。内容里没有它时,参数以 `ARGUMENTS: ` 追加在末尾 |
| `$ARGUMENTS[N]` | 按 0 起始的下标取单个参数 |
| `$N` | `$ARGUMENTS[N]` 的简写 |
| `$name` | `arguments` frontmatter 里声明的命名参数 |
| `${CLAUDE_SESSION_ID}` | 当前会话 ID |
| `${CLAUDE_EFFORT}` | 当前 effort level |
| `${CLAUDE_SKILL_DIR}` | 包含 SKILL.md 的目录 |
| `${CLAUDE_PROJECT_DIR}` | 项目根目录 |
`${CLAUDE_SKILL_DIR}` 有一个很实用的用法:它在 skill 正文和 `allowed-tools` 里**都会**被替换,所以能让 skill 无提示地运行自己捆绑的脚本:
```markdown
---
name: render-chart
description: Render a chart from a CSV file
allowed-tools: Bash(${CLAUDE_SKILL_DIR}/scripts/render.sh *)
---
Run `${CLAUDE_SKILL_DIR}/scripts/render.sh ` to render the chart.
```
两处的 `${CLAUDE_SKILL_DIR}` 展开成同一个目录,于是 allow 规则精确匹配 skill 正文让 Claude 运行的那条命令,脚本无需权限提示就能跑。
这个替换需要 v2.1.129 或更高版本。旧版本上规则保持字面 `${CLAUDE_SKILL_DIR}` 字符串,永远匹配不上,命令仍然要确认权限。
下标参数用 shell 风格的引号规则,多词值要用引号包成一个参数:`/my-skill "hello world" second` 让 `$0` 展开成 `hello world`、`$1` 展开成 `second`。
要写字面的 `$` 加数字,用反斜杠转义:`\$1.00`。
### 堆叠调用
[Section titled “堆叠调用”](#堆叠调用)
一条消息开头可以叠几个 skill。敲 `/write-tests /fix-issue 123` 会加载两个 skill,把尾部文本 `123` 作为 `$ARGUMENTS` 传给每一个。
Claude Code 展开第一个 skill 加后面最多五个。展开在第一个不是「内联 user-invocable skill」的 token 处停止——所以 fork 成 subagent 运行的 skill(如 `/code-review`)、或者参数本身可能以斜杠命令开头的 skill(如 `/loop`)也会终止展开。那个 token 及之后的一切成为所有已展开 skill 的参数文本。
## 辅助文件
[Section titled “辅助文件”](#辅助文件)
skill 目录里可以放多个文件:
```plaintext
my-skill/
├── SKILL.md # 必需,概览与导航
├── reference.md # 详细 API 文档,需要时才加载
├── examples.md # 用法示例
└── scripts/
└── helper.py # 脚本,被执行而不是被加载
```
在 SKILL.md 里引用这些文件,让 Claude 知道每个文件装了什么、什么时候该加载:
```markdown
## Additional resources
- For complete API details, see [reference.md](reference.md)
- For usage examples, see [examples.md](examples.md)
```
SKILL.md 控制在 500 行以内,详细参考资料移到单独文件。
## 在 subagent 里跑 skill
[Section titled “在 subagent 里跑 skill”](#在-subagent-里跑-skill)
`context: fork` 让 skill 在隔离上下文里运行,skill 内容成为驱动 subagent 的提示词。它拿不到你的对话历史。
fork 出的 subagent 默认在后台运行:你继续工作,结果完成时回到对话。设 `background: false` 则在调用它的那一轮等结果。
几种情况下 Claude Code 会等结果,即使没设 `background: false`:
* 非交互模式(`-p` 标志或 Agent SDK)
* 设了 `CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=1`
* 上一次同名 skill 的调用还在运行时又调用它
* 定时任务以这个 skill 为提示词触发
踩坑预警
后台运行的 fork skill 应用的编辑在会话检查点之外,`/rewind` **撤不掉**。要还原只能用 git。
另外后台 fork 用的是适用于后台 subagent 的更窄工具集。skill 的步骤依赖那个集合之外的工具时,设 `background: false` 保留完整工具集。
`context: fork` 只对有明确指令的 skill 有意义。如果 skill 装的是「用这些 API 约定」这类指导而没有任务,subagent 收到指导但没有可执行的提示词,会没有实质产出就返回。
skill 和 subagent 有两个配合方向:
| 方式 | 系统提示来自 | 任务是 | 另外加载 |
| ----------------------- | ---------------------- | ------------ | ------------------------------------- |
| skill 配 `context: fork` | agent 类型 | SKILL.md 内容 | CLAUDE.md(agent 是 Explore 或 Plan 时除外) |
| subagent 配 `skills` 字段 | subagent 的 markdown 正文 | Claude 的委派消息 | 预加载的 skills + CLAUDE.md |
内置的 Explore 和 Plan agent 跳过 CLAUDE.md 和 git status 以保持上下文小,所以用 `agent: Explore` 的 fork skill 只看到 SKILL.md 内容和 agent 自己的系统提示。
## 限制 Claude 能调用哪些 skill
[Section titled “限制 Claude 能调用哪些 skill”](#限制-claude-能调用哪些-skill)
三种方式。
全部禁用,在 `/permissions` 里 deny `Skill` 工具:
```plaintext
Skill
```
按名字允许或拒绝:
```plaintext
# 只允许特定 skill
Skill(commit)
Skill(review-pr *)
# 拒绝特定 skill
Skill(deploy *)
```
`Skill(name)` 精确匹配,`Skill(name *)` 前缀匹配加任意参数。
第三种是在 frontmatter 里加 `disable-model-invocation: true`,这会把 skill 完全从 Claude 的上下文里移除。
### 从设置里覆盖可见性
[Section titled “从设置里覆盖可见性”](#从设置里覆盖可见性)
`skillOverrides` 从设置控制可见性,不改 skill 自己的 frontmatter。适合那些你不想编辑的 skill,比如提交在共享仓库里的:
| 值 | 列给 Claude | 在 `/` 菜单 |
| ----------------------- | --------- | -------- |
| `"on"` | 名字和描述 | 是 |
| `"name-only"` | 只有名字 | 是 |
| `"user-invocable-only"` | 隐藏 | 是 |
| `"off"` | 隐藏 | 隐藏 |
```json
{
"skillOverrides": {
"legacy-context": "name-only",
"deploy": "off"
}
}
```
`/skills` 菜单会帮你写:高亮一个 skill 按空格循环状态,回车保存到 `.claude/settings.local.json`。
不在 `skillOverrides` 里的 skill 视为 `"on"`。插件 skill 不受 `skillOverrides` 影响,那些用 `/plugin` 管。
## 描述被截断的问题
[Section titled “描述被截断的问题”](#描述被截断的问题)
Claude Code 把一份 skill 名字和描述的列表加载进上下文,让 Claude 知道有什么可用。列表**总是包含每个 skill 的名字**,但 skill 很多时描述会被缩短以适应字符预算,可能剥掉 Claude 匹配你请求所需的关键词。
预算按模型上下文窗口的 1% 缩放。列表溢出时,Claude Code 从**你调用最少**的 skill 开始丢描述,所以最常用的保留完整文本。
诊断和调整:
* `/doctor` 给出列表上下文成本的估计和最大贡献者
* `/context` 的 Skills 行报告预算应用**之后**的列表大小,与模型实际收到的一致
* 提高预算:`skillListingBudgetFraction`(如 `0.02` 表示 2%)或 `SLASH_COMMAND_TOOL_CHAR_BUDGET` 环境变量设固定字符数
* 给其他 skill 腾预算:把低优先级的设成 `"name-only"`
* 从源头精简:把关键用例写在最前面,每条的合并文本上限 1536 字符(可用 `skillListingMaxDescChars` 调)
## skill 不触发怎么查
[Section titled “skill 不触发怎么查”](#skill-不触发怎么查)
1. 检查 description 里有没有用户会自然说出的关键词
2. 问一句「What skills are available?」确认它在列表里
3. 把请求改得更贴近 description
4. 如果它是 user-invocable,直接用 `/skill-name` 调用
如果 frontmatter YAML 格式坏了,Claude Code 会加载 skill 正文但元数据为空——所以 `/skill-name` 仍然能用,但 Claude 没有描述可以匹配。用 `--debug` 看解析错误。
反过来触发太频繁:把 description 写得更具体,或者加 `disable-model-invocation: true`。
## 怎么知道 skill 真的有用
[Section titled “怎么知道 skill 真的有用”](#怎么知道-skill-真的有用)
看到 skill 触发,只说明 Claude 找到了它,不说明它做了你想要的事。要判断 skill 是否有效,分开测两件事:
1. Claude 在该触发的提示上是否调用了它
2. 调用时输出是否符合预期
两者的检验方法都是基线对比:收集几个真实提示,每个在干净会话里跑两次——一次有这个 skill、一次禁用它,对比结果。
干净会话很重要,因为你在写 skill 时留下的上下文会掩盖书面指令里的缺口。
官方的 `skill-creator` 插件把这个对比循环自动化了:
```plaintext
/plugin install skill-creator@claude-plugins-official
```
它会把测试用例存在 skill 目录的 `evals/evals.json`、每个用例开一个 subagent 保证干净上下文、记录 token 数和耗时、把「有 skill vs 没 skill」的通过率与开销聚合到 `benchmark.json`,还能对两个版本做盲测 A/B。
## 变式练习
[Section titled “变式练习”](#变式练习)
* 写一个用 `` !`git diff HEAD` `` 注入真实 diff 的 skill,然后对比它和「直接问 Claude 我改了什么」的回答质量。动态注入的价值在这个对比里最明显。
* 给一个有副作用的 skill(部署、发消息)加 `disable-model-invocation: true`,然后试着让 Claude 自己触发它,确认它拒绝。
* 在一个会话里连续调用五六个不同 skill,然后触发一次压缩,用 `/context` 看哪些被保留了。这能让 25000 token 的重新附加预算从数字变成体感。
## 还没确认的点
[Section titled “还没确认的点”](#还没确认的点)
* `hooks` frontmatter 字段的完整配置格式。文档指向另一页(Hooks in skills and agents),本文未核实。
* `paths` frontmatter 在 skill 上的行为与 `.claude/rules/` 上的是否完全一致。文档说用同样的格式,但触发时机是否相同没有明确说明。
* 压缩后重新附加时「前 5000 token」的截断边界。是按 token 硬切还是按段落边界,未见说明。
* `effort` 字段各档位在不同模型上的可用性。文档说取决于模型,没有给出对应表。
## 参考
[Section titled “参考”](#参考)
* [Extend Claude with skills - Claude Code Docs](https://code.claude.com/docs/en/skills)
* [How Claude remembers your project - Claude Code Docs](https://code.claude.com/docs/en/memory)
# Claude Code subagent:上下文隔离到什么程度、fork 和普通 subagent 差在哪、三种限额各管什么
> subagent 启动时到底加载了什么、Explore 和 Plan 为什么跳过 CLAUDE.md、后台 subagent 的工具集会被二次收窄、以及会话总量 / 并发 / 嵌套深度三个独立限额。
适用范围
适用工具:Claude Code CLI(v2.1.x 系列)。最后核对日期:2026-07-30。核实来源:官方 sub-agents 文档。前置假设:读过[skills](/agents/skills/),知道 skill 和 subagent 是两个不同的机制。
## 结论
[Section titled “结论”](#结论)
用 subagent 的判断标准是一句话:**这个side任务会不会用一堆你之后不会再看的搜索结果、日志、文件内容淹没主对话**。会,就交给 subagent——它在自己的上下文里干活,只把摘要返回。
但隔离是双向的。subagent 看不到你的对话历史、看不到你已经调用的 skill、看不到 Claude 已经读过的文件。所以它需要的背景信息必须在委派时说清楚。
`fork` 是这条规则的例外,也是它存在的理由:fork 继承整个对话,并且因为系统提示和工具定义与父会话完全一致,它的第一次请求能复用父会话的 prompt 缓存——所以对于需要相同上下文的任务,fork 比新起一个 subagent 更便宜。
## 内置 subagent
[Section titled “内置 subagent”](#内置-subagent)
| agent | 模型 | 工具 | 用途 |
| --------------- | -------------------------- | ------------------ | --------------- |
| Explore | 继承主对话(Claude API 上封顶 Opus) | 只读,Write 和 Edit 被拒 | 文件发现、代码搜索、代码库探索 |
| Plan | 继承主对话 | 只读 | plan 模式下的代码库调研 |
| general-purpose | 继承主对话 | subagent 可用的全部工具 | 需要探索加修改的复杂多步任务 |
**Explore 和 Plan 跳过你的 CLAUDE.md 文件和父会话的 git status**,其他所有内置和自定义 subagent 都加载这两样。
这个设计的理由是「让调研又快又便宜」:探索阶段不需要知道你的代码规范,加载它们只是浪费 token 和时间。
推论很实际:**主对话是带着完整 CLAUDE.md 上下文读 Explore 和 Plan 的结果的,所以大部分规则不需要传到 subagent 里面**。但如果某条规则必须到位,比如「忽略 `vendor/` 目录」,就要在委派时的提示词里重述一遍。
Explore 的模型行为在 v2.1.198 变过:以前总是跑 Haiku,现在继承主对话的模型。Claude API 上继承值封顶 Opus——主对话在更高档时 Explore 跑 Opus,主对话在 Sonnet 或 Haiku 时跑同一个。想让探索固定在低成本模型上,定义一个自己的 `Explore`(用户或项目层)覆盖内置的,并写 `model: haiku`。
关掉内置 Explore 和 Plan:`CLAUDE_CODE_DISABLE_EXPLORE_PLAN_AGENTS=1`,之后 Claude 直接读文件而不委派。
## 定义文件与优先级
[Section titled “定义文件与优先级”](#定义文件与优先级)
| 位置 | 范围 | 优先级 |
| ------------------------------ | ------ | ----- |
| managed 设置里的 `.claude/agents/` | 组织全体 | 1(最高) |
| `--agents` CLI 标志 | 当前会话 | 2 |
| `.claude/agents/` | 当前项目 | 3 |
| `~/.claude/agents/` | 你的所有项目 | 4 |
| 插件的 `agents/` 目录 | 插件启用处 | 5(最低) |
项目 subagent 从当前工作目录往上走查找,直到仓库根,每一级 `.claude/agents/` 都会被扫。多个嵌套目录定义了同名 subagent 时,用**离工作目录最近**的那个定义。
`.claude/agents/` 和 `~/.claude/agents/` 是递归扫描的,可以用 `agents/review/`、`agents/research/` 这样的子文件夹组织。子目录路径不影响 subagent 的标识和调用方式——**身份只来自 `name` frontmatter 字段**。
踩坑预警
`name` 值必须在整棵树里唯一。同一个 `.claude/agents/` 目录(含其子文件夹)下两个文件声明了同一个 `name`,Claude Code 只加载其中一个,选哪个取决于**文件系统读取顺序**,没有文档化的优先级。
`/doctor` 会报告同目录下同名的文件并建议重命名或删除。这个问题不会报错,只会让你困惑「为什么改了 subagent 定义没生效」。
插件的 `agents/` 目录不同:子文件夹会成为作用域标识符的一部分。插件 `my-plugin` 里的 `agents/review/security.md` 注册为 `my-plugin:review:security`。
`name` 里不能有 `:`,那是插件作用域标识符的保留字符。v2.1.218 之前这种名字是被接受的。
## frontmatter 全字段
[Section titled “frontmatter 全字段”](#frontmatter-全字段)
只有 `name` 和 `description` 是必填的。
| 字段 | 说明 |
| ----------------- | ----------------------------------------------------------------------------- |
| `name` | 小写字母和连字符组成的唯一标识。hook 收到的 `agent_type` 就是这个值。文件名不必匹配 |
| `description` | Claude 何时该委派给这个 subagent |
| `tools` | 可用工具。省略时继承 subagent 可用的全部工具 |
| `disallowedTools` | 要拒绝的工具,从继承或指定的列表里移除 |
| `model` | `sonnet` / `opus` / `haiku` / `fable` / 完整 model ID / `inherit`。默认 `inherit` |
| `permissionMode` | `default` / `acceptEdits` / `auto` / `dontAsk` / `bypassPermissions` / `plan` |
| `maxTurns` | 停止前的最大 agentic 轮数 |
| `skills` | 启动时预加载进上下文的 skill。注入的是**完整内容**,不只是描述 |
| `mcpServers` | 这个 subagent 可用的 MCP 服务器。可以是已配置服务器的名字,也可以是内联定义 |
| `hooks` | 限定在这个 subagent 生命周期内的 hook |
| `memory` | 持久记忆作用域:`user` / `project` / `local` |
| `background` | `true` 时总在后台运行。未设时 Claude 决定,v2.1.198 起默认后台 |
| `effort` | 这个 subagent 活跃时的 effort level |
| `isolation` | 设 `worktree` 在临时 git worktree 里运行 |
| `color` | 任务列表和 transcript 里的显示颜色 |
| `initialPrompt` | 作为主会话 agent 运行时自动提交的第一轮用户输入 |
插件 subagent **不支持** `hooks`、`mcpServers`、`permissionMode` 三个字段,出于安全原因加载时被忽略。需要它们就把 agent 文件复制到 `.claude/agents/` 或 `~/.claude/agents/`。
## 工具集会被过滤两次
[Section titled “工具集会被过滤两次”](#工具集会被过滤两次)
这一节是 subagent 行为里最容易造成困惑的地方:**同一份定义在前台和后台可能解析出不同的工具集**。
subagent 继承主对话的内置工具和 MCP 工具,然后经过两道过滤。
**第一道**移除这些工具,即使你在 `tools` 里列了也一样:
* `Agent`(在深度上限时)
* `AskUserQuestion`
* `EndConversation`
* `EnterPlanMode`
* `ExitPlanMode`(除非 `permissionMode` 是 `plan`)
* `ScheduleWakeup`
* `TaskOutput`
* `WaitForMcpServers`
* `Workflow`
**第二道**只对后台运行的 subagent 生效(这是默认)。后台 subagent 保留全部 MCP 工具,但内置工具只剩:`Read`、`Grep`、`Glob`、`Bash`、`PowerShell`、`Edit`、`Write`、`NotebookEdit`、`WebFetch`、`WebSearch`、`TodoWrite`、`Skill`、`ToolSearch`、`EnterWorktree`、`ExitWorktree`、`Monitor`、`TaskStop`、`SendMessage`、`Artifact`。
其他内置工具全被移除,无论是继承的还是在 `tools` 里列的。**移除不报错**,除非最后 `tools` 列表解析结果为空。
fork 跳过这两道过滤,拿到主对话的完整工具池。
限制工具用 `tools` 做白名单或 `disallowedTools` 做黑名单:
```yaml
---
name: safe-researcher
description: Research agent with restricted capabilities
tools: Read, Grep, Glob, Bash
---
```
```yaml
---
name: no-writes
description: Inherits the available tools except file writes
disallowedTools: Write, Edit
---
```
两个都设时:先应用 `disallowedTools`,再在剩余池里解析 `tools`。两边都列的工具被移除。
两个字段都接受 MCP 服务器级模式:`mcp__` 或 `mcp____*` 授予或移除该服务器的全部工具。`disallowedTools` 里的 `mcp__*` 移除所有服务器的全部 MCP 工具。
`tools` 里没有任何条目解析成工具时(比如全拼错了),Claude Code 通常拒绝启动这个 subagent,`Agent` 工具返回一个点名未解析条目的错误。v2.1.208 之前那个 subagent 会带着零工具启动,返回空的或令人困惑的结果。
## 把 MCP 服务器限定给 subagent
[Section titled “把 MCP 服务器限定给 subagent”](#把-mcp-服务器限定给-subagent)
`mcpServers` 字段有一个很实用的用法:**让一个 MCP 服务器完全不进主对话,避免它的工具描述在那里消耗上下文**。
```yaml
---
name: browser-tester
description: Tests features in a real browser using Playwright
mcpServers:
# 内联定义:只对这个 subagent 可见
- playwright:
type: stdio
command: npx
args: ["-y", "@playwright/mcp@latest"]
# 按名引用:复用已配置的服务器
- github
---
Use the Playwright tools to navigate, screenshot, and interact with pages.
```
内联定义的服务器在 subagent 启动时连接、结束时断开。字符串引用共享父会话的连接。
subagent 拿到工具,父对话拿不到。这比在 `.mcp.json` 里定义再想办法屏蔽要干净。
主会话适用的 MCP 限制同样覆盖 subagent frontmatter 里声明的服务器:`--strict-mcp-config`、`--bare`、企业 managed MCP 配置、`allowedMcpServers` 和 `deniedMcpServers` 策略。
## 权限模式的继承规则
[Section titled “权限模式的继承规则”](#权限模式的继承规则)
subagent 继承主对话的权限上下文,可以覆盖模式,但有两个例外:
* 父会话用 `bypassPermissions` 或 `acceptEdits` 时,**这个优先,无法被覆盖**
* 父会话用 auto mode 时,subagent 继承 auto mode,frontmatter 里的 `permissionMode` **被忽略**——分类器用与父会话相同的规则评估 subagent 的工具调用
这个设计防的是「通过 subagent 提权」:如果 subagent 能声明比父会话更宽的权限模式,那么权限模式这个机制就形同虚设了。
## 预加载 skill 与「反向」用法
[Section titled “预加载 skill 与「反向」用法”](#预加载-skill-与反向用法)
```yaml
---
name: api-developer
description: Implement API endpoints following team conventions
skills:
- api-conventions
- error-handling-patterns
---
Implement API endpoints. Follow the conventions and patterns from the preloaded skills.
```
注入的是每个 skill 的**完整内容**,不只是描述。
这个字段控制的是「预加载哪些」,不是「能访问哪些」:不设它,subagent 仍然能在执行期间通过 `Skill` 工具发现并调用项目、用户、插件 skill。要彻底禁止,从 `tools` 里省掉 `Skill` 或加到 `disallowedTools`。
设了 `disable-model-invocation: true` 的 skill **不能被预加载**,因为预加载取自「Claude 能调用的 skill」这同一个集合。内置的 `/verify` 和 `/code-review` 也在其中。
这和「在 subagent 里跑 skill」是互为反向的两种用法:
| | 系统提示来自 | 任务是 |
| ----------------------- | ---------------------- | ------------ |
| subagent 配 `skills` 字段 | subagent 的 markdown 正文 | Claude 的委派消息 |
| skill 配 `context: fork` | 你指定的 agent 类型 | SKILL.md 内容 |
底层是同一套系统,区别在谁提供系统提示、谁提供任务。
## 持久记忆
[Section titled “持久记忆”](#持久记忆)
`memory` 字段给 subagent 一个跨对话存续的目录:
```yaml
---
name: code-reviewer
description: Reviews code for quality and best practices
memory: user
---
You are a code reviewer. As you review code, update your agent memory with
patterns, conventions, and recurring issues you discover.
```
| 作用域 | 位置 | 何时用 |
| --------- | ------------------------------------ | -------------- |
| `user` | `~/.claude/agent-memory//` | 跨所有项目记住 |
| `project` | `.claude/agent-memory//` | 项目特定且可通过版本控制共享 |
| `local` | `.claude/agent-memory-local//` | 项目特定但不进版本控制 |
`project` 是推荐的默认值,因为它让 subagent 的知识可以通过版本控制共享。
subagent 记忆是[自动记忆](/agents/claude-md-context/)的一部分:关掉自动记忆(`autoMemoryEnabled` 或 `CLAUDE_CODE_DISABLE_AUTO_MEMORY`),`memory` 字段就无效了。
启用时:
* 系统提示包含读写记忆目录的指令
* 系统提示还包含记忆目录里 `MEMORY.md` 的前 200 行或 25KB(先到者为准)
* `Read`、`Write`、`Edit` 工具自动启用,让 subagent 能管理自己的记忆文件
把记忆指令直接写进 subagent 的 markdown 正文,让它主动维护自己的知识库:
```plaintext
Update your agent memory as you discover codepaths, patterns, library
locations, and key architectural decisions. Write concise notes about
what you found and where.
```
## 启动时到底加载了什么
[Section titled “启动时到底加载了什么”](#启动时到底加载了什么)
非 fork subagent 的初始上下文包含:
* **系统提示**:agent 自己的提示加上 Claude Code 附加的环境细节,**不是**完整的 Claude Code 系统提示
* **任务消息**:Claude 交接工作时写的委派提示
* **CLAUDE.md 文件**:主对话加载的每一层,含 `~/.claude/CLAUDE.md`、项目规则、`CLAUDE.local.md`、managed 策略文件。Explore 和 Plan 跳过
* **git status**:父会话开始时的快照。不在 git 仓库里或 `includeGitInstructions` 为 false 时没有。Explore 和 Plan 无论如何都跳过
* **预加载的 skill**:`skills` 字段里点名的 skill 的完整内容
* **兄弟名册**:列出 `main` 和会话里其他所有具名 agent 的系统提醒,每个都是 `SendMessage` 的合法 `to` 值
有几样主对话状态**永远不到**非 fork subagent:
* **输出样式**:subagent 跑自己的系统提示,你的输出样式不影响它的回复
* **自动记忆**:主对话的自动记忆不加载。要给 subagent 自己的持久记忆用 `memory` 字段
* **上下文窗口大小**:subagent 的窗口由它自己的模型决定,不是父会话的。委派给窗口更小的模型,那个 subagent 就只有更小的窗口
最后一条容易忽略:给一个需要读大量内容的任务指定 `model: haiku` 省钱,可能因为窗口不够而失败。
## 三种限额,各管一件事
[Section titled “三种限额,各管一件事”](#三种限额各管一件事)
这三个是独立的,各有自己的环境变量。混起来会误判问题在哪。
| 限额 | 默认 | 变量 | 管什么 |
| ---- | --- | --------------------------------------- | --------------- |
| 会话总量 | 200 | `CLAUDE_CODE_MAX_SUBAGENTS_PER_SESSION` | 一个会话里累计能起多少个 |
| 并发 | 20 | `CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS` | 同时运行多少个 |
| 嵌套深度 | 3 | `CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH` | subagent 能往下嵌几层 |
**会话总量**:已完成的 subagent 仍然计数。达到上限时 `Agent` 工具报 `Subagent spawn limit reached`,错误信息告诉 Claude 用自己的工具完成剩余工作。`/clear` 重置计数。
**并发**:达到上限时报 `Concurrent subagent limit reached`,错误告诉 Claude 不要重试。运行数降下来之后又能起。恢复一个已完成的 subagent 会拿一个新槽位且**不检查限额**,所以恢复操作可以把运行数推过上限。
**嵌套深度**:默认 3 层。到达深度上限时,Claude Code 对除 fork 以外的每个 subagent 收回 `Agent` 工具,所以在上限的 subagent 自己干完委派的活并返回一份摘要。设 `1` 关掉嵌套。
嵌套的历史默认值变过好几次:v2.1.172 到 v2.1.216 默认可嵌套最多 5 层且不可改;v2.1.217 到 v2.1.218 默认是 1;v2.1.219 提到 3。看老资料时注意这一点。
## fork:继承一切的那种 subagent
[Section titled “fork:继承一切的那种 subagent”](#fork继承一切的那种-subagent)
```plaintext
/subtask draft unit tests for the parser changes so far
```
fork 继承到目前为止的整个对话,而不是从零开始。它放弃了 subagent 本来提供的输入隔离:fork 看到与主会话相同的系统提示、工具、模型、消息历史,所以你可以把一个 side 任务交给它而不必重新解释情况。
fork 自己的工具调用仍然留在主对话之外,只有最终结果回来,所以主上下文窗口保持干净。
| | fork | 具名 subagent |
| --------- | ---------- | --------------- |
| 上下文 | 完整对话历史 | 全新,带你传的提示词 |
| 系统提示和工具 | 与主会话相同 | 来自定义文件,后台运行时被过滤 |
| 模型 | 与主会话相同 | 来自 `model` 字段 |
| prompt 缓存 | **与主会话共享** | 独立缓存 |
共享 prompt 缓存这一条是 fork 的成本优势来源:系统提示和工具定义与父会话完全一致,所以第一次请求复用父会话的缓存。
什么时候用 fork:具名 subagent 需要太多背景才能有用时,或者你想从同一个起点并行试几种方案时。
fork 出现在提示输入框下面的面板里,后台运行。控制键:
| 键 | 动作 |
| ----- | ------------------------------- |
| ↑ / ↓ | 在行之间移动 |
| Enter | 打开选中 fork 的 transcript 并给它发后续消息 |
| x | 移除已完成的 fork 或停止运行中的 |
| Esc | 焦点回到提示输入框 |
打开某个 fork 的 transcript 时,后续消息和 skill 发给那个 agent,但内置命令仍在主对话里跑。v2.1.199 起,在那个视图里敲 `/model` 或 `/fast` 会提示它改的是主对话而不是所看的 agent。
fork 不能再 fork。
命令名变过:v2.1.212 起是 `/subtask`;v2.1.161 到 v2.1.211 是 `/fork`。现在的 `/fork` 在 agent view 开启时是把整个会话复制成一个新的后台会话,语义不同了。
## 显式调用的三档
[Section titled “显式调用的三档”](#显式调用的三档)
自动委派不够用时,三种方式,从一次性建议升级到会话级默认:
**自然语言**:点名 subagent,Claude 决定是否委派。
```plaintext
Use the test-runner subagent to fix failing tests
```
**@-mention**:保证那个 subagent 为这一个任务运行。
```plaintext
@"code-reviewer (agent)" look at the auth changes
```
注意:你的完整消息仍然发给 Claude,由 Claude 根据你的要求写 subagent 的任务提示。**@-mention 控制的是调用哪个 subagent,不是它收到什么提示词**。
**会话级**:整个会话用那个 subagent 的系统提示、工具限制和模型。
```bash
claude --agent code-reviewer
```
这时 subagent 的系统提示**完全替换**默认的 Claude Code 系统提示,和 `--system-prompt` 一样。CLAUDE.md 和项目记忆仍然通过正常消息流加载。
要让它成为项目里每个会话的默认值,在 `.claude/settings.json` 里设 `agent`:
```json
{ "agent": "code-reviewer" }
```
CLI 标志优先于设置。
## 输出扫描:一层容易忽略的防御
[Section titled “输出扫描:一层容易忽略的防御”](#输出扫描一层容易忽略的防御)
Claude Code 在 Claude 读到之前扫描每个 subagent 的最终报告。
理由是:subagent 可能读了你从未审查的文件、网页或命令输出,那些来源的文本可以携带针对主对话的指令。
扫描从不删除或改写内容,只做两种可见的改动:
* **插入反斜杠**:模仿 Claude Code 自己输出的文本(如 `` 标签,或以 `Human:` / `Assistant:` 开头的行)会被插入反斜杠,让模仿读作普通文本而不被当成对话的一部分
* **标记行**:报告模仿 `` 这类标签、或提到 `bypassPermissions`、`--dangerously-skip-permissions` 这类权限设置时,前面加一行 `[harness: subagent output matched instruction-shaped pattern(s):`
扫描**不判断内容是否恶意**,也不改变报告里的指令能做什么:报告引导 Claude 做出的工具调用仍然经过会话的权限检查和沙箱。它不是「限制 subagent 能碰什么」的替代品。
需要 v2.1.210 或更高版本。
## 什么时候不该用 subagent
[Section titled “什么时候不该用 subagent”](#什么时候不该用-subagent)
用主对话,当:
* 任务需要频繁来回或迭代打磨
* 多个阶段共享大量上下文(规划、实现、测试)
* 你在做一个快速的定点改动
* **延迟要紧**。subagent 从零开始,可能需要时间收集上下文
用 subagent,当:
* 任务产生你不需要留在主上下文里的冗长输出
* 你想强制特定的工具限制或权限
* 工作自成一体,能返回一份摘要
想要「可复用的提示词或流程,但在主对话上下文里跑」,用 [skill](/agents/skills/) 而不是 subagent。
对话里已有内容的快速提问,用 `/btw`:它看得到你的完整上下文但没有工具访问,答案会被丢弃而不加入历史。
并行调研的隐藏成本
「并行开几个 subagent 调研不同模块」是个好模式,但有一个反直觉的代价:**subagent 完成时结果回到主对话**。开很多 subagent、每个都返回详细结果,消耗的上下文也很可观。
所以并行调研的价值取决于「返回的是摘要还是详情」。在委派时明确要求摘要,而不是指望 subagent 自己判断。
需要持续并行、或者工作量超过你的上下文窗口时,该用的是 agent teams——每个 worker 有自己独立的上下文。
## 恢复 subagent
[Section titled “恢复 subagent”](#恢复-subagent)
每次调用创建一个新实例,上下文全新。要接着已有 subagent 的工作而不是重来,让 Claude 恢复它。恢复的 subagent 保留完整对话历史,包括之前所有工具调用、结果和推理。
内置 Explore 和 Plan 是一次性的,不返回 agent ID,因此不能恢复。需要接着做时用 general-purpose 或自定义 subagent。
transcript 存在 `~/.claude/projects/{project}/{sessionId}/subagents/`,每个文件是 `agent-{agentId}.jsonl`。
subagent transcript 独立于主对话存续:
* 主对话压缩时 subagent transcript 不受影响,它们在单独的文件里
* transcript 在会话内存续,重启 Claude Code 后恢复同一个会话即可恢复 subagent
* 超过 `cleanupPeriodDays`(默认 30 天)后被自动删除
v2.1.198 起,subagent 把来自启动它的 agent 的消息当作正常任务指导,包括任务中途的纠偏。两条限制始终成立:**没有任何 agent 消息算作你对待处理权限提示的批准**;**没有任何 agent 消息能改变 subagent 的权限设置、CLAUDE.md 或配置**。
## 一个实用模式:hook 做条件校验
[Section titled “一个实用模式:hook 做条件校验”](#一个实用模式hook-做条件校验)
`tools` 字段的粒度是「工具级」,有时不够——比如想允许 Bash 但只允许只读 SQL。这时用 `PreToolUse` hook:
```yaml
---
name: db-reader
description: Execute read-only database queries
tools: Bash
hooks:
PreToolUse:
- matcher: "Bash"
hooks:
- type: command
command: "./scripts/validate-readonly-query.sh"
---
```
```bash
#!/bin/bash
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
if echo "$COMMAND" | grep -iE '\b(INSERT|UPDATE|DELETE|DROP|CREATE|ALTER|TRUNCATE)\b' > /dev/null; then
echo "Blocked: Only SELECT queries are allowed" >&2
exit 2
fi
exit 0
```
macOS 和 Linux 上记得 `chmod +x`,否则 hook 是**失败**而不是拦截——这个区别很关键,失败的 hook 拦不住任何东西。
系统提示里也告诉 subagent 拒绝写请求,hook 作为兜底。两层都要有:系统提示让它主动配合,hook 保证它做不到。
要让项目层 subagent 的 frontmatter hook 运行,需要接受该文件夹的 workspace 信任对话框。`~/.claude/agents/` 里的用户层 subagent 和 `--agents` 传入的定义不需要这一步。v2.1.218 之前 frontmatter hook 可以从未信任的文件夹运行。
## 变式练习
[Section titled “变式练习”](#变式练习)
* 让一个 subagent 跑测试套件,只报告失败的测试和错误信息,对比直接在主对话里跑测试后 `/context` 的差异。这是 subagent 上下文价值最直观的一次测量。
* 定义一个自己的 `Explore` 覆盖内置的,写 `model: haiku`,看探索质量和成本的变化。
* 用 `/subtask` 起一个 fork,同时在主会话继续工作,体会「共享缓存」在响应速度上的差别。
## 还没确认的点
[Section titled “还没确认的点”](#还没确认的点)
* `initialPrompt` 字段的完整行为。文档说它作为主会话 agent 运行时自动提交为第一轮,命令和 skill 会被处理,但与用户提供的提示词如何拼接的细节没有核实。
* `isolation: worktree` 的清理条件。文档说 subagent 没做改动时 worktree 会被自动清理,「有改动」的判定标准未见说明。
* 兄弟名册(sibling roster)的完整触发条件。确认了它需要 `SendMessage` 在工具列表里且至少一个其他 agent 有名字,但名册内容的更新时机只知道是启动时快照。
* 并发限额下「恢复已完成 subagent 不检查限额」是否会导致实际运行数无上限。文档陈述了这个行为,没有说明是否有其他兜底。
## 参考
[Section titled “参考”](#参考)
* [Create custom subagents - Claude Code Docs](https://code.claude.com/docs/en/sub-agents)
* [Extend Claude with skills - Claude Code Docs](https://code.claude.com/docs/en/skills)
# Claude Code 专题
> 从「能用起来」到「敢让它无人值守跑」,中间是一批具体的配置字段和边界。
[接入 DeepSeek 后端 ](./deepseek-backend/)ANTHROPIC\_BASE\_URL 怎么配,以及区域限制报错怎么绕开
[四层配置:谁覆盖谁 ](./settings-files/)五级优先级、标量覆盖与数组合并的区别、settings.json 和 .claude.json 的职责边界
[hooks 完全指南 ](./hooks/)事件时机、退出码 2 的专用语义、通过 stdout JSON 控制会话流转
[六种权限模式 ](./permission-modes/)auto mode 的分类器机制、和 bypassPermissions 差在哪、为什么项目配置不能自己开 auto
[接 MCP 服务器 ](./mcp-servers/)三种 scope 存在哪、四种传输怎么选、tool search 如何避免上下文被吃满
[用量为什么会飙高 ](./costs-and-usage/)长会话的五个成本来源,以及按收益排序的应对手段
[放进脚本和 CI ](./headless-ci/)-p 模式的输出格式、双重预算上限、CI 里该用哪种认证
# Claude Code 用量为什么会莫名飙高:长会话的五个成本来源与对应手段
> /usage 与 /context 各看什么、订阅用户和 API 用户看到的数字含义不同、以及长会话里长上下文、缓存未命中、定时任务、teammate、压缩这五个成本来源的具体机制。
适用范围
适用工具:Claude Code CLI(v2.1.x 系列)。最后核对日期:2026-07-30。核实来源:官方 costs 文档。前置假设:用过一段时间 Claude Code,对「用量」有过疑问。
## 结论
[Section titled “结论”](#结论)
「我今天没干什么,为什么用量这么高」这个问题,答案通常是同一个:**Claude Code 每次请求都带上完整对话历史,而且每次 Claude 用工具都是一次新请求,那次请求也带着这批工具结果**。
所以在一个开了一整天的会话里问一句一行的问题,代价是整个会话的历史长度。有 prompt caching 兜底,历史按缓存价重读,但仍然计入用量。
推论也很直接:**不相关的任务之间 `/clear`,是省钱手段里投入产出最高的一个**,而且 `/clear` 本身不花钱。
## 先搞清楚你在看什么数字
[Section titled “先搞清楚你在看什么数字”](#先搞清楚你在看什么数字)
`/usage` 顶部的 Session 区块显示本次会话的 token 统计:
```plaintext
Total cost: $0.55
Total duration (API): 6m 20s
Total duration (wall): 6h 33m 10s
Total code changes: 0 lines added, 0 lines removed
Usage by model:
claude-sonnet-4-6: 1.2k input, 5.3k output, 940.0k cache read, 50.0k cache write ($0.55)
```
关于这个数字有三件事必须知道:
1. **它是本地算出来的**。Claude Code 拿 token 数按标准列表价乘出来,不反映促销价或合同折扣,所以和实际账单可能不一样。权威账单看 Claude Console 的 Usage 页。
2. **Max 和 Pro 订阅用户不用看它**。用量已包含在订阅里,这个美元数字对计费没有意义。订阅用户该看的是同一屏上的 plan usage bars 和用量分解。
3. **`/clear` 之后归零**。v2.1.211 之前这些总数会跨 `/clear` 一直累积到进程结束——如果你的印象是「这个数字只涨不降」,那是旧版本行为。
`API duration` 和 `wall duration` 的巨大差距(6 分钟 vs 6.5 小时)本身就是信息:说明会话开了很久但实际请求很少。这种会话正是「一句话的代价是整个历史」的典型场景。
订阅计划上 `/usage` 还会把最近用量归因到 skills、subagents、plugins 和各个 MCP 服务器,每项显示占比。某个行为占到 10% 以上时会被标出来,比如 long context 或 cache misses。按 `d` / `w` 在 24 小时和 7 天之间切换。
这些数字是从**本机**的会话历史算出来的,其他设备和 claude.ai 上的用量不在内。
`/context` 是另一个命令,看的是当前上下文里什么在占空间。排查「是不是某个 MCP 服务器把上下文吃掉了」用它。
## 长会话里用量爬升的五个来源
[Section titled “长会话里用量爬升的五个来源”](#长会话里用量爬升的五个来源)
这五条是分开的机制,对应的手段也不同。
### 1. 长上下文
[Section titled “1. 长上下文”](#1-长上下文)
每次请求带完整对话,每次工具使用又是一次带着工具结果的请求。这是最主要的来源。
手段:`/clear`。或者 `/compact` 带自定义指令保留关键部分。
### 2. 缓存未命中
[Section titled “2. 缓存未命中”](#2-缓存未命中)
休息时间超过缓存生命周期后,第一条消息会未命中缓存,重新处理完整上下文。
缓存生命周期分档:
* 订阅计划:1 小时
* 开始动用 usage credits 之后:降到 5 分钟
* API key 或云厂商:默认 5 分钟
这解释了一个常见困惑:午饭前后同样问一句话,午饭后那次的代价明显更高。因为中间隔了一小时以上,缓存过期了。
Pro 和 Max 计划上,长时间中断后恢复一个大会话时,Claude Code 会提供「从摘要恢复」的选项,让后续请求不必携带完整历史。
### 3. 定时任务
[Section titled “3. 定时任务”](#3-定时任务)
定时任务按自己的间隔触发,会话空闲时也会触发,每次都发送完整上下文。
一个配了定时任务的长会话是在持续产生用量,即使你没在用。
### 4. agent teammate
[Section titled “4. agent teammate”](#4-agent-teammate)
每个活跃的 teammate 都在消耗 token,直到它退出或会话结束。
teammate 在 plan 模式下运行时,agent team 的 token 用量大约是标准会话的 **7 倍**——每个 teammate 维护自己的上下文窗口、作为独立的 Claude 实例运行。
所以「活干完了就关掉 teammate」不是礼节问题,是成本问题。
### 5. 压缩本身
[Section titled “5. 压缩本身”](#5-压缩本身)
`/compact` 要读取它要总结的对话,所以压缩一个大上下文本身就是一次大请求。
推论:如果你想要的是「从头开始」而不是「保留连续性」,用 `/clear`,它不花钱。为了省钱去 `/compact` 一个巨大的上下文,方向是反的。
## 省钱手段,按收益排序
[Section titled “省钱手段,按收益排序”](#省钱手段按收益排序)
### 不相关任务之间 `/clear`
[Section titled “不相关任务之间 /clear”](#不相关任务之间-clear)
前面说过了,这是收益最高的一条。陈旧上下文在之后的每一条消息上都在浪费 token。
想保留会话以后再回来:先 `/rename` 起个能找到的名字,再 `/clear`,之后用 `/resume` 回去。
### 模型选型
[Section titled “模型选型”](#模型选型)
Sonnet 处理大多数编码任务都够,成本低于 Opus。把 Opus 留给复杂架构决策和多步推理。
会话中途用 `/model` 切换,默认值在 `/config` 里设。简单的 subagent 任务在配置里指定 `model: haiku`。
「Opus 忘了切回来」是 API 计费和云厂商计费下超支的两大常见原因之一(另一个是长会话没清)。
### 把冗长操作交给 subagent
[Section titled “把冗长操作交给 subagent”](#把冗长操作交给-subagent)
跑测试、抓文档、处理日志文件都会产生大量输出。交给 subagent,冗长输出留在 subagent 的上下文里,只有摘要回到主对话。
这是 subagent 除了「分工」之外的第二个价值,而且这个价值更容易量化。
### 用 hook 做预处理
[Section titled “用 hook 做预处理”](#用-hook-做预处理)
hook 可以在 Claude 看到数据之前先过滤。Claude 读一个一万行的日志找报错,和一个 hook 先 grep 出 ERROR 行再给它,上下文差异是几万 token 对几百 token。
官方给的例子是一个 `PreToolUse` hook 改写测试命令,只保留失败信息:
```bash
#!/bin/bash
input=$(cat)
cmd=$(echo "$input" | jq -r '.tool_input.command')
if [[ "$cmd" =~ ^(npm test|pytest|go test) ]]; then
filtered_cmd="$cmd 2>&1 | grep -A 5 -E '(FAIL|ERROR|error:)' | head -100"
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"allow\",\"updatedInput\":{\"command\":\"$filtered_cmd\"}}}"
else
echo "{}"
fi
```
注意这里用的是 `updatedInput` 改写命令本身,不是过滤输出。这个 [hook 的用法](/claude-code/hooks/)比想象的更灵活。
验证方法:`/hooks` 看它在不在 `PreToolUse` 下,或者用 `claude --debug` 跑一次 `npm test`,debug 日志里会出现 `modified tool input keys: [command]`。
### 把 CLAUDE.md 里的专项指令搬进 skill
[Section titled “把 CLAUDE.md 里的专项指令搬进 skill”](#把-claudemd-里的专项指令搬进-skill)
CLAUDE.md 在会话开始时加载进上下文。里面写着 PR review 或数据库迁移的详细流程,那些 token 在你做完全不相关的工作时也在占位。
skill 是按需加载的,只在被调用时进上下文。把专项流程搬过去,基础上下文就小了。
CLAUDE.md 控制在 200 行以内。
### 优先用 CLI 工具而不是 MCP 服务器
[Section titled “优先用 CLI 工具而不是 MCP 服务器”](#优先用-cli-工具而不是-mcp-服务器)
`gh`、`aws`、`gcloud`、`sentry-cli` 这类 CLI 工具比对应的 MCP 服务器更省上下文,因为它们不增加任何 per-tool 列表开销。Claude 直接跑命令就行。
[MCP 的 tool search](/claude-code/mcp-servers/) 默认延迟加载工具定义,已经把这个开销压得很低了,但 CLI 仍然是零开销。
用 `/mcp` 看配了哪些服务器,把不用的关掉。
### 调整 extended thinking
[Section titled “调整 extended thinking”](#调整-extended-thinking)
thinking token 按输出 token 计费,默认预算可以达到每请求数万 token。
简单任务不需要深度推理时可以降:`/effort` 或 `/model` 里调 effort level、`/config` 里关掉 thinking、或者对固定 thinking 预算的模型设 `MAX_THINKING_TOKENS=8000`。
自适应推理的模型会忽略非零预算,那些模型上要用 effort level 而不是设预算。
### 写具体的 prompt
[Section titled “写具体的 prompt”](#写具体的-prompt)
「改进这个代码库」触发大范围扫描。「给 `auth.ts` 里的 login 函数加输入校验」让 Claude 用最少的文件读取完成工作。
这条听起来像老生常谈,但它在成本上的影响是数量级的。
## 复杂任务上避免走错路
[Section titled “复杂任务上避免走错路”](#复杂任务上避免走错路)
走错方向产生的 token 是纯浪费。四个习惯:
* **复杂任务先用 plan 模式**。`Shift+Tab` 切过去,让 Claude 先探索并提出方案给你确认,避免方向错了之后昂贵的返工。
* **早点纠偏**。方向不对立刻按 Escape 停下。用 `/rewind` 或双击 Escape 回到之前的检查点。
* **给验证目标**。测试用例、截图、期望输出。Claude 能自己验证时,它在你需要提出修正之前就发现了问题。
* **增量测试**。写一个文件、测一次、再继续。问题在便宜的时候被发现。
## 空闲时也有开销
[Section titled “空闲时也有开销”](#空闲时也有开销)
Claude Code 在空闲时也会用一点 token:
* 对话摘要:为 `claude --resume` 功能总结之前对话的后台任务
* 命令处理:`/usage` 这类命令本身会产生请求
这些通常每会话低于 $0.04。
## 一个数量级参考
[Section titled “一个数量级参考”](#一个数量级参考)
企业部署的平均成本大约是每开发者每活跃日 $13、每人每月 $150-250,90% 的用户保持在每活跃日 $30 以下。
这组数字的用途是判断自己是否异常。远超这个范围时,大概率是前面五个来源里的某一个,而不是「Claude Code 就是这么贵」。
要估自己团队的开销,从小规模试点开始,用上面的工具建立基线再推广。
## 变式练习
[Section titled “变式练习”](#变式练习)
* 在一个开了几小时的会话里跑 `/usage`,看 `API duration` 和 `wall duration` 的比值。比值越极端,越说明你在为历史付费。
* 同一个任务做两次:一次在干净会话里,一次在挂了半天的会话里。对比 `/usage` 的数字。这个对比做过一次,`/clear` 的习惯就养成了。
* 跑 `/context` 看当前上下文构成。如果 MCP 工具或 CLAUDE.md 的占比让你意外,那就是你的优化起点。
## 还没确认的点
[Section titled “还没确认的点”](#还没确认的点)
* OpenTelemetry 导出的具体指标名与配置细节。官方 costs 页提到它是唯一能把 per-user token 和成本指标近实时导入自己观测栈的方案,但指标清单在 monitoring 页,本文没有核实。
* `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` 的确切作用范围。这个变量在其他资料里被提及,官方 costs 页没有列出,未核实。
* 「agent team 约为标准会话 7 倍」这个数字的测量条件。文档说的是 teammate 在 plan 模式下运行时,其他模式下的倍数没有给出。
* prompt caching 的缓存命中判定细节。缓存生命周期的分档确认了,但「什么样的上下文变化会导致缓存失效」没有查到明确说明。
## 参考
[Section titled “参考”](#参考)
* [Manage costs effectively - Claude Code Docs](https://code.claude.com/docs/en/costs)
* [Claude Code settings - Claude Code Docs](https://code.claude.com/docs/en/settings)
# Claude Code 接入 DeepSeek:ANTHROPIC_BASE_URL 怎么配,以及区域限制报错怎么绕开
> 三个环境变量把 Claude Code 接到 DeepSeek 的 Anthropic 兼容端点上,模型名会被自动映射;顺带说清楚 unsupported_country_region_territory 报错的真实原因。
适用范围
适用工具:Claude Code CLI,后端为 DeepSeek 提供的 Anthropic 兼容 API(`https://api.deepseek.com/anthropic`)。最后核对日期:2026-07-30。前置假设:已安装 Claude Code,有一个 DeepSeek 的 API Key。
## 结论
[Section titled “结论”](#结论)
三个环境变量就能把 Claude Code 切到 DeepSeek:`ANTHROPIC_BASE_URL` 指向 DeepSeek 的 Anthropic 兼容端点,`ANTHROPIC_API_KEY` 放 DeepSeek 的密钥。不用改任何代码,模型名会在 DeepSeek 服务端自动映射到对应的 DeepSeek 模型。写进 `settings.json` 的 `env` 字段比每次开终端手动 `export` 更持久,也更容易在多台机器之间同步。
## 三个环境变量,认清各自角色
[Section titled “三个环境变量,认清各自角色”](#三个环境变量认清各自角色)
| 变量 | 角色 | 是否本文需要 |
| ---------------------- | --------------------------------------------------------------------- | ---------------------------------------------- |
| `ANTHROPIC_BASE_URL` | 请求发到哪个服务端,默认是官方 Anthropic API | 需要,改成 DeepSeek 的端点 |
| `ANTHROPIC_API_KEY` | 官方认证方式下使用的密钥 | 需要,放 DeepSeek 的 Key |
| `ANTHROPIC_AUTH_TOKEN` | 自定义授权时,会作为 `Authorization` 头的 Bearer token 发出,用于对接第三方网关的鉴权方式和官方不一致的场景 | DeepSeek 的兼容端点直接用 `ANTHROPIC_API_KEY` 即可,不需要这个 |
这三个变量都属于 Claude Code 官方文档列出的环境变量,`ANTHROPIC_BASE_URL` 和 `ANTHROPIC_AUTH_TOKEN` 明确是为「自定义 Anthropic API 部署」场景准备的。
## 配置方式一:shell 里 export(临时,验证用)
[Section titled “配置方式一:shell 里 export(临时,验证用)”](#配置方式一shell-里-export临时验证用)
```bash
export ANTHROPIC_BASE_URL="https://api.deepseek.com/anthropic"
export ANTHROPIC_API_KEY="sk-你的DeepSeekKey"
claude
```
关掉终端就失效,适合先跑一次确认能不能用,不适合长期依赖。
## 配置方式二:写进 settings.json(推荐,持久生效)
[Section titled “配置方式二:写进 settings.json(推荐,持久生效)”](#配置方式二写进-settingsjson推荐持久生效)
在用户级配置(`~/.claude/settings.json`,Windows 下是 `C:\Users\<用户名>\.claude\settings.json`)或项目级配置(项目目录下的 `.claude/settings.json`)里写:
```json
{
"env": {
"ANTHROPIC_BASE_URL": "https://api.deepseek.com/anthropic",
"ANTHROPIC_API_KEY": "sk-你的DeepSeekKey"
}
}
```
`settings.json` 里 `env` 字段的值会在 Claude Code 启动时被设置为对应的环境变量。配置来源有优先级:企业管理策略 > 命令行参数 > 本地项目设置(`.claude/settings.local.json`)> 共享项目设置(`.claude/settings.json`)> 用户设置(`~/.claude/settings.json`)。想让整个团队共用同一份,放共享项目设置里提交进仓库;只想自己用,放用户设置或者本地项目设置(后者默认在 `.gitignore` 里)。
## 怎么确认配置真的生效了
[Section titled “怎么确认配置真的生效了”](#怎么确认配置真的生效了)
不要只看「能跑起来」就当作生效,因为报错和正常响应看起来都可能是别的原因导致的。跑一次会话后执行:
```plaintext
/status
```
看输出里配置来源那一行,确认当前生效的 `ANTHROPIC_BASE_URL` 确实来自你改的那份配置文件,而不是被更高优先级的配置覆盖了。这比直接问 Claude「你现在用的是什么模型」更可靠,因为模型本身不知道自己被路由到了哪里,问出来的答案是训练数据里的自我认知,不是运行时的真实状态。
## 区域限制报错:unsupported\_country\_region\_territory
[Section titled “区域限制报错:unsupported\_country\_region\_territory”](#区域限制报错unsupported_country_region_territory)
走官方 Anthropic API 或 AWS Bedrock 直连时,可能会遇到这个报错:
```plaintext
API Error: 400 {"type":"error","error":{"type":"invalid_request_error","message":"Access to Anthropic models is not allowed from unsupported countries, regions, or territories. Please refer to https://www.anthropic.com/supported-countries for more information."}}
```
这是基于请求来源地域的合规限制,不是网络代理配置错了,也不是 CLI 的 bug。社区排查记录里有个反直觉的证据:同一份配置,请求经过 AWS 宁夏区域(`cn-northwest-1`)时报错,换成首尔区域(`ap-northeast-2`)后完全正常跑通,说明拦截点是在服务端识别请求来源地域时触发的,不是本机网络层面的问题。Anthropic 官方在对应的 GitHub issue 里把这个问题标记为「按预期设计」(not planned)关闭,即这是既定的地域策略,不算需要修复的 bug。
走 DeepSeek 这类第三方 Anthropic 兼容端点时,请求根本不经过官方 Anthropic 的地域检测,从目前验证的情况看不会触发这个报错。这不是「换后端修复了限制」,只是换了一条不经过该检测点的路径,官方的地域策略本身没有变化。
踩坑预警
这个结论只核实过 DeepSeek 这一条路径,不代表所有第三方网关都能绕开限制。不同网关自己的地域策略、是否二次转发到官方 Anthropic,都可能影响结果,换别的后端时建议重新验证一次。
## settings.json 与 .claude.json 的分工(简述)
[Section titled “settings.json 与 .claude.json 的分工(简述)”](#settingsjson-与-claudejson-的分工简述)
`settings.json` 是行为配置文件,本文用到的 `env` 字段,以及权限、hooks、model 等设置都写在这里。`.claude.json` 是 CLI 自身维护的状态文件,记录登录态、项目信任记录之类的运行时信息,通常不需要手动改。这条分工来自社区资料的归纳,没有找到官方文档逐字确认 `.claude.json` 的字段结构,标注为待核实。
## 还没确认的点
[Section titled “还没确认的点”](#还没确认的点)
* `.claude.json` 的具体字段结构,没有找到官方文档逐条列出,本文的描述只是社区总结的角色分工。
* DeepSeek 的模型名映射规则细节,属于 DeepSeek 服务端行为,是否会随版本调整没有做长期跟踪。
* 是否所有 OpenAI/Anthropic 兼容后端都能规避地域限制,只验证了 DeepSeek 这一条。
## 参考
[Section titled “参考”](#参考)
* [Claude Code settings - Claude Code Docs](https://code.claude.com/docs/en/settings)
* [Environment variables - Claude Code Docs](https://code.claude.com/docs/en/env-vars)
* [The Anthropic API - DeepSeek API Docs](https://api-docs.deepseek.com/guides/anthropic_api)
* [GitHub issue #2656 - anthropics/claude-code](https://github.com/anthropics/claude-code/issues/2656)
# 把 Claude Code 放进脚本和 CI:-p 模式的输出格式、预算上限与认证方式
> claude -p 的三种输出格式怎么选、--max-turns 和 --max-budget-usd 各拦什么、--bare 与 --safe-mode 的区别,以及 CI 里该用哪种认证。
适用范围
适用工具:Claude Code CLI(v2.1.x 系列)。最后核对日期:2026-07-30。核实来源:官方 CLI reference。前置假设:读过[权限模式](/claude-code/permission-modes/),知道无人值守场景下权限提示是个问题。
## 结论
[Section titled “结论”](#结论)
`claude -p "query"` 跑完就退出,不进交互界面。这是把 Claude Code 放进脚本、CI、git hook 的基础。
但从「能跑」到「能放心让它无人值守跑」,中间有三件事必须做对:
1. **成本双保险**:`--max-turns` 拦轮数,`--max-budget-usd` 拦花钱。两个都要设,因为它们拦的不是同一件事。
2. **认证方式**:CI 里用 `claude setup-token` 生成的长期 token 或 API key,不能依赖交互式登录。
3. **权限策略**:无人值守时没人能回答权限提示,要么用 `--allowedTools` 白名单,要么用 `--permission-prompt-tool` 让一个 MCP 工具代答。
## 基本调用形态
[Section titled “基本调用形态”](#基本调用形态)
```bash
# 一次性查询
claude -p "explain this function"
# 管道输入
cat logs.txt | claude -p "find the root cause of the errors"
# 继续最近一次对话
claude -c -p "check for type errors"
# 按 ID 或名字恢复会话
claude -r "auth-refactor" "finish this PR"
```
`-c` 是 `--continue`,加载当前目录最近的对话。`-r` 是 `--resume`,按会话 ID 或名字恢复。
## 三种输出格式
[Section titled “三种输出格式”](#三种输出格式)
`--output-format` 三个取值,用途完全不同。
**`text`**(默认):只打印回复文本。适合人看,或者你只需要最终答案。
**`json`**:结构化输出,一次性返回。适合脚本解析。
```bash
claude -p "list the exported functions in src/api.ts" --output-format json
```
**`stream-json`**:实时事件流。适合要显示进度、或者要在中途做处理的场景。
```bash
claude -p --output-format stream-json --verbose "run the test suite and summarize failures"
```
`stream-json` 有一批配套标志,都要求同时带 `--verbose`:
| 标志 | 作用 |
| ---------------------------- | -------------------------------------------------------- |
| `--include-partial-messages` | 包含流式的部分消息事件 |
| `--include-hook-events` | 包含所有 hook 生命周期事件 |
| `--forward-subagent-text` | 输出 subagent 的文本和 thinking 块,可以重建每个 subagent 的 transcript |
| `--replay-user-messages` | 把 stdin 的用户消息回显到 stdout 用于确认 |
| `--prompt-suggestions` | 每轮之后输出一条预测的下一句用户输入 |
`--forward-subagent-text` 值得留意:不加它,你只能看到 subagent 的 `tool_use` 和 `tool_result` 块,看不到它在想什么。调试「subagent 为什么给了这个结论」时需要它。需要 v2.1.211 或更高版本。
`SessionStart` 和 `Setup` 的 hook 事件总是包含在流里,不需要 `--include-hook-events`。
## 要固定输出结构,用 —json-schema
[Section titled “要固定输出结构,用 —json-schema”](#要固定输出结构用-json-schema)
`--output-format json` 给的是 Claude Code 的响应包装结构,不保证回复内容本身的形状。要让模型的输出符合一个 schema:
```bash
claude -p --json-schema '{"type":"object","properties":{"severity":{"type":"string"},"files":{"type":"array","items":{"type":"string"}}}}' \
"analyze the security issues in this diff"
```
这个功能只在 print 模式可用。
两个细节:
* schema 无效时 Claude Code 直接报错退出。v2.1.205 之前它会静默产出非结构化输出,不报错——这是个很难发现的坑。
* `format` 关键字被接受,但只作为注解,不做客户端校验。所以 `"format": "email"` 不会真的验证邮箱格式。
## 两个上限,拦的是两件事
[Section titled “两个上限,拦的是两件事”](#两个上限拦的是两件事)
这是无人值守场景最重要的一节。
**`--max-turns`** 限制 agentic 轮数,达到上限报错退出:
```bash
claude -p --max-turns 3 "fix the failing test"
```
默认无限制。
用 `--input-format stream-json` 时有一个行为要知道:Claude 工作期间发来的消息会排队,当前轮次因为上限结束时,那条消息作为**自己的一轮**开始运行,有自己独立的上限。v2.1.205 之前那条消息会被丢弃。
**`--max-budget-usd`** 限制 API 花费:
```bash
claude -p --max-budget-usd 5.00 "refactor the auth module"
```
subagent 的花费计入这个上限。达到上限后:
* 再起 subagent 会失败,报 `Budget limit reached`
* 仍在运行的后台 subagent 被停止
上限强制行为需要 v2.1.217 或更高版本。
为什么两个都要设?
它们拦的维度不同,任一个单独用都有漏洞。
只设 `--max-turns 10`:如果每一轮都在读巨大的文件、或者用了 Opus 加高 effort,十轮可能花掉远超预期的钱。
只设 `--max-budget-usd 5`:如果任务陷入一个便宜的死循环(反复跑一个失败的小命令),预算慢慢烧,而你本来希望它早点失败退出。
CI 里两个都设,是让「失败」这件事变得可预测。
## CI 里的认证
[Section titled “CI 里的认证”](#ci-里的认证)
CI 环境不能走交互式登录。两种方式:
**长期 OAuth token**(需要 Claude 订阅):
```bash
claude setup-token
```
这条命令把 token 打印到终端,**不保存**。把它放进 CI 的 secret 存储。
**API key**:直接用 Anthropic API key 计费。
官方明确说明:第三方产品(包括基于 Agent SDK 构建的 agent)除事先获批准外,不允许提供 claude.ai 登录或使用其速率额度。所以如果你在构建给别人用的东西,用 API key。
检查认证状态:
```bash
claude auth status
```
它输出 JSON,登录时退出码 0,未登录 1。加 `--text` 得到人类可读的输出。这个退出码语义让它可以直接用在 CI 的前置检查里。
## 让脚本启动更快
[Section titled “让脚本启动更快”](#让脚本启动更快)
两个标志都是「关掉自动发现」,但关的范围不同,用途也不同。
**`--bare`**:跳过 hooks、skills、plugins、MCP 服务器、自动记忆、CLAUDE.md 的自动发现。Claude 保留 Bash、文件读、文件编辑工具。
```bash
claude --bare -p "reformat this JSON"
```
用途是**让脚本调用启动更快**。一个只需要处理文本的一次性调用,不需要加载整个项目的配置。
**`--safe-mode`**:关掉所有自定义来排查配置问题。
```bash
claude --safe-mode
```
它和 `--bare` 的关键差别:`--safe-mode` 下认证、模型选择、内置工具、权限**正常工作**,而且 managed 设置策略仍然生效(含策略配置的 hook、status line)。managed 插件、managed skill、managed CLAUDE.md、策略配置的 MCP 服务器不加载。
所以:脚本用 `--bare`,排查配置用 `--safe-mode`。
`--safe-mode` 有一个具体用途值得记住:检查某个自定义配置是不是导致了自动模型回退。
还有一个更细的控制:`--setting-sources user,project` 明确指定加载哪些设置层。
## 系统提示:四个标志与一个判断
[Section titled “系统提示:四个标志与一个判断”](#系统提示四个标志与一个判断)
| 标志 | 行为 |
| ----------------------------- | ------------- |
| `--system-prompt` | 替换整个默认提示 |
| `--system-prompt-file` | 用文件内容替换 |
| `--append-system-prompt` | 追加到默认提示后 |
| `--append-system-prompt-file` | 把文件内容追加到默认提示后 |
`--system-prompt` 和 `--system-prompt-file` 互斥。追加类标志可以和替换类组合。
判断标准很清楚:**Claude Code 的默认身份还适合你的任务吗**。
用追加:Claude 仍然是一个编码助手,只是多遵守你的额外规则。追加保留了默认的工具指导、安全指令和编码约定,你只提供差异部分。
用替换:任务的界面、身份或权限模型与 Claude Code 不同,比如一个流水线里无人监督的非编码 agent。替换会丢掉全部默认提示,包括工具指导和安全指令,你要为任务还需要的一切负责。
subagent 也有对应的标志:`--append-subagent-system-prompt` 追加文本到每个 subagent 的系统提示末尾,含嵌套的。它**只在 `-p` 非交互模式下生效**,需要 v2.1.205 或更高版本。
## 多用户场景的缓存优化
[Section titled “多用户场景的缓存优化”](#多用户场景的缓存优化)
`--exclude-dynamic-system-prompt-sections` 把每机器相关的部分(工作目录、环境信息、记忆路径、git 仓库标记)从系统提示移到第一条用户消息里。
作用是**改善 prompt 缓存复用**:不同用户、不同机器跑同一个任务时,系统提示变成完全一致的,缓存能命中。
只对默认系统提示生效,设了 `--system-prompt` 或 `--system-prompt-file` 时被忽略。适合脚本化的多用户工作负载。
## 权限:无人值守时怎么办
[Section titled “权限:无人值守时怎么办”](#权限无人值守时怎么办)
三种方案,安全性递减:
**白名单**:明确列出不需要确认的工具。
```bash
claude -p --allowedTools "Bash(git log *)" "Bash(git diff *)" "Read" "analyze recent commits"
```
`--allowedTools` 是「不询问就执行」,`--tools` 是「限制哪些工具可用」。两个不同的语义,容易混。
要限制可用工具用 `--tools`:
```bash
claude --tools "Bash,Edit,Read"
```
`--tools ""` 禁用全部内置工具,`--tools "default"` 是全部。这个标志**不影响 MCP 工具**——要连 MCP 一起禁,用 `--disallowedTools "mcp__*"`,或者传 `--strict-mcp-config` 但不给 `--mcp-config`,这样没有 MCP 服务器加载。
**MCP 代答**:让一个 MCP 工具处理权限提示。
```bash
claude -p --permission-prompt-tool mcp_auth_tool "query"
```
Claude Code 会等那个工具的 MCP 服务器连上才跑第一轮,上限是 `MCP_TIMEOUT` 的 30 秒启动超时。v2.1.206 之前,一个启动慢的服务器会让运行以「MCP 工具未找到」的错误退出。
一个限制:这个工具**不能批准标记为需要用户交互的 MCP 工具**——Claude Code 会把对那类工具的 allow 结果转成 deny。需要 v2.1.199 或更高版本。
**跳过全部**:`--dangerously-skip-permissions`。只在一次性容器里用,理由见[权限模式](/claude-code/permission-modes/)。
## 在 CI 里跑 setup hook
[Section titled “在 CI 里跑 setup hook”](#在-ci-里跑-setup-hook)
三个和 hook 相关的 print 模式标志:
| 标志 | 作用 |
| --------------- | ---------------------------------------- |
| `--init` | 会话前跑带 `init` matcher 的 Setup hook |
| `--maintenance` | 会话前跑带 `maintenance` matcher 的 Setup hook |
| `--init-only` | 跑 Setup 和 SessionStart hook 后退出,不开始对话 |
`--init-only` 的用途是「预热」:在 CI 的一个单独步骤里跑完初始化,后续步骤直接开始工作。
## 会话持久化
[Section titled “会话持久化”](#会话持久化)
默认情况下会话保存到磁盘可以恢复。CI 里通常不需要:
```bash
claude -p --no-session-persistence "query"
```
这个标志只在 print 模式可用。环境变量 `CLAUDE_CODE_SKIP_PROMPT_HISTORY` 在任何模式下都有同样效果。
需要固定会话 ID 时(比如要在后续步骤恢复):
```bash
claude --session-id "550e8400-e29b-41d4-a716-446655440000"
```
必须是合法 UUID。
## 后台会话
[Section titled “后台会话”](#后台会话)
`--bg` 把会话作为后台 agent 启动并立即返回,打印会话 ID 和管理命令:
```bash
claude --bg "investigate the flaky test"
```
**`--bg` 不能和 `-p` 组合**。两者的语义冲突:`-p` 是「跑完就退」,`--bg` 是「丢到后台继续跑」。
配套的管理命令:
| 命令 | 作用 |
| --------------------- | -------------------------------------------------- |
| `claude agents` | 打开 agent view 监控和派发并行后台会话。`--json` 打印活跃会话的 JSON 数组 |
| `claude attach ` | 在当前终端接入一个后台会话 |
| `claude logs ` | 打印后台会话的近期输出 |
| `claude stop ` | 停止后台会话 |
| `claude respawn ` | 重启后台会话,对话历史保留 |
| `claude rm ` | 从列表移除,transcript 仍在本地 |
`claude agents --json` 是脚本化的入口。`claude respawn --all` 重启所有运行中的会话,典型用途是让它们用上更新后的 Claude Code 二进制。
`--exec` 是另一个方向:把一个 shell 命令作为 PTY 支持的后台作业跑,而不是启动 Claude 会话。
```bash
claude --bg --exec 'pytest -x'
```
## 一个容易踩的命令解析问题
[Section titled “一个容易踩的命令解析问题”](#一个容易踩的命令解析问题)
如果你把 `claude` 别名成带 `--dangerously-skip-permissions` 的形式,`claude daemon status` 这类子命令在旧版本上不会执行——v2.1.199 之前,`daemon ` 会被当成新交互式会话的提示词。
v2.1.199 起 `claude --dangerously-skip-permissions daemon ` 正确路由到 daemon 子命令。但**只有**前置的 `--dangerously-skip-permissions` 或 `--allow-dangerously-skip-permissions` 会这样路由,其他前置标志仍然启动交互式会话。
另外 `claude --help` **不列出全部标志**。一个标志不在 `--help` 里,不代表它不可用。这一点在排查「文档提到的标志报错」时很关键——先确认版本,而不是怀疑文档。
## 变式练习
[Section titled “变式练习”](#变式练习)
* 写一个 git pre-commit hook 用 `claude -p --output-format json` 检查暂存的 diff,设好 `--max-turns 2` 和 `--max-budget-usd 0.20`。跑几次看上限是不是符合预期。
* 用 `--json-schema` 让输出固定成一个你能直接喂给下游脚本的结构,对比不用 schema 时解析的脆弱程度。
* 同一个任务分别用 `--bare` 和不加对比启动耗时。如果差距明显,说明你的项目配置加载成本值得关注。
## 还没确认的点
[Section titled “还没确认的点”](#还没确认的点)
* `--output-format json` 返回结构的完整字段。CLI reference 说明了格式选项,字段清单在 Agent SDK 文档里,本文未核实。
* `stream-json` 各事件类型的完整定义。同上。
* print 模式下的退出码语义。只确认了 `claude auth status`(0 登录 / 1 未登录)和 `claude ultrareview`(0 成功 / 1 失败)这两个子命令,`claude -p` 本身在各类失败下的退出码没有查到明确说明。
* `--max-budget-usd` 的计价基准。它是否和 `/usage` 一样按标准列表价本地计算,文档未见说明。
## 参考
[Section titled “参考”](#参考)
* [CLI reference - Claude Code Docs](https://code.claude.com/docs/en/cli-reference)
* [Agent SDK overview - Claude Code Docs](https://code.claude.com/docs/en/sdk/sdk-overview)
# Claude Code hooks 完全指南:事件时机、退出码语义、JSON 输出协议
> hooks 在 settings.json 里的配置结构、全部生命周期事件的触发时机、退出码 0/2 与其他值的区别、以及通过 stdout JSON 控制会话流转的完整字段。
适用范围
适用工具:Claude Code CLI(v2.1.x 系列)。最后核对日期:2026-07-30。核实来源:官方 hooks 文档。前置假设:读过[配置优先级](/claude-code/settings-files/),知道 `settings.json` 分几层。
## 结论
[Section titled “结论”](#结论)
CLAUDE.md 是建议层,hooks 是强制层。这句话是理解 hooks 存在意义的全部。
写在 CLAUDE.md 里的「提交前必须跑 lint」是一条上下文里的指令,模型读到了,通常会听,但不保证。写成 hook 的同一条规则是一个在固定时机被 shell 执行的命令,模型的意图不参与其中。要「一定发生」的事情,写 hook;要「希望模型知道」的事情,写 CLAUDE.md。
## 配置结构
[Section titled “配置结构”](#配置结构)
hooks 配置在 `settings.json` 里,三层嵌套:事件名 → matcher 组 → hook 数组。
```json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/check-bash.sh",
"timeout": 10
}
]
}
]
}
}
```
字段含义:
| 字段 | 说明 |
| --------- | ----------------------------------------------- |
| `matcher` | 匹配工具名。对不涉及工具的事件(如 `SessionStart`)匹配的是该事件自己的取值 |
| `type` | `command` 执行 shell 命令。也支持 HTTP 类型的 hook,见下文 |
| `command` | 要执行的命令。用 `$CLAUDE_PROJECT_DIR` 引用项目根,避免依赖当前工作目录 |
| `timeout` | 超时秒数。超时后该 hook 被中断,不影响同组其他 hook |
同一个 matcher 组里的多个 hook 并行执行。matcher 支持正则,`Edit|Write` 这样写可以一次匹配两个工具。
`$CLAUDE_PROJECT_DIR` 这个变量值得单独说:hook 命令的当前工作目录不保证是项目根,用相对路径写脚本位置在子目录里启动会话时会失效。始终用这个变量拼绝对路径。
## 事件与触发时机
[Section titled “事件与触发时机”](#事件与触发时机)
事件的价值全在时机上。同一个脚本挂在 `PreToolUse` 和 `PostToolUse` 上,能做的事情完全不同——前者能拦住操作,后者只能在操作已经发生后反应。
| 事件 | 时机 | 典型用途 |
| -------------------- | ---------------- | ---------------------------- |
| `PreToolUse` | 工具调用参数已定、尚未执行 | 拦截危险命令、校验参数 |
| `PostToolUse` | 工具执行完成 | 格式化刚写入的文件、跑增量测试 |
| `UserPromptSubmit` | 你的输入提交后、模型看到之前 | 注入当前分支、issue 编号等动态上下文 |
| `Stop` | 模型认为该轮结束、即将交还控制权 | 检查任务是否真的完成,不完成就打回去继续 |
| `SubagentStop` | subagent 结束 | 同上,作用在子任务粒度 |
| `SessionStart` | 会话开始 | 注入启动上下文 |
| `SessionEnd` | 会话结束 | 清理临时资源 |
| `PreCompact` | 压缩发生前 | 抢救即将被压掉的关键信息 |
| `Notification` | Claude Code 发通知时 | 转发到自己的通知渠道 |
| `PermissionRequest` | 权限提示即将弹出 | 按自己的规则自动决策 |
| `ConfigChange` | 检测到设置文件改动 | 配置变更审计 |
| `InstructionsLoaded` | 指令文件加载完成 | 调试 CLAUDE.md / rules 到底加载了哪些 |
`SessionStart` 的 matcher 有四个取值,对应四种不同的「开始」:
* `startup`:全新启动
* `resume`:恢复已有会话
* `clear`:执行 `/clear` 之后
* `compact`:自动压缩之后
`compact` 这个取值有一个额外用途:它是判断压缩是否真的发生过的可观测信号。挂一个只写时间戳到日志的 hook 在 `SessionStart:compact` 上,就有了压缩事件的时间线。
`InstructionsLoaded` 是配置调试专用的。它能记录哪些指令文件被加载、什么时候加载、为什么加载——排查 path-scoped rules 和子目录里延迟加载的 CLAUDE.md 时,这比猜要快得多。
## 退出码语义
[Section titled “退出码语义”](#退出码语义)
hook 的退出码是最简单的控制方式,三档:
| 退出码 | 行为 |
| ---- | ------------------------ |
| 0 | 成功。stdout 在多数事件里不进入模型上下文 |
| 2 | 阻塞。stderr 的内容回灌给模型 |
| 其他非零 | 非阻塞错误。stderr 展示给你,会话继续 |
退出码 2 是整套 hooks 机制里最需要理解的一个数字。它不只是「失败」,而是「失败了,并且把失败原因告诉模型,让模型据此调整」。
举例:`PreToolUse` 上的 hook 以 2 退出并向 stderr 写 `禁止直接 push 到 main,请先建分支`,模型收到的不是一个笼统的「操作被拒绝」,而是这句具体的话,因此有机会自己改成建分支再提交。而如果用退出码 1,模型只知道 hook 出错了,不知道该怎么改。
所以写 hook 时 stderr 的措辞值得认真对待——那是给模型看的指令,不是给人看的日志。
## JSON 输出协议
[Section titled “JSON 输出协议”](#json-输出协议)
退出码只能表达「过 / 不过」。要做更细的控制,让 hook 向 stdout 输出 JSON。
通用字段:
| 字段 | 作用 |
| ---------------- | ---------------------------- |
| `continue` | `false` 时停止后续处理 |
| `stopReason` | 配合 `continue: false`,说明停止原因 |
| `suppressOutput` | 不在 transcript 里显示这个 hook 的输出 |
| `systemMessage` | 向你(不是模型)展示一条消息 |
事件专属字段里,最有用的三个:
**`PreToolUse` 的 `permissionDecision`**,取值 `allow` / `deny` / `ask`。这让 hook 变成一个可编程的权限决策器:把团队的规则写成脚本,比在 `permissions.allow` 里堆字符串模式灵活得多,因为它能读取当前分支、时间、文件内容等任何运行时状态。
```json
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "main 分支上禁止直接改 migration 文件"
}
}
```
**`UserPromptSubmit` 和 `SessionStart` 的 `additionalContext`**,把字符串注入模型上下文。这是给会话喂动态信息的正规通道——当前 sprint 的目标、正在修的 issue 描述、构建状态,都可以在每次提交输入时自动带上,不必手写进 prompt。
**`Stop` 的 `decision: "block"`**,配合 `reason` 阻止模型结束这一轮。这是[让 agent 自己判断是否收工](/agents/goal-command/)那类玩法的底层机制:Stop Hook 检查验收条件,不满足就带着具体原因把控制权推回给模型。
踩坑预警
`Stop` hook 里写「无条件 block」会导致会话无法结束。这个 hook 必须有一个能够被满足的退出条件,而且这个条件要能被脚本客观判断(文件存在、测试通过、某个标记写入),不能是「任务看起来做完了」。
调试这类 hook 时先让它只打日志不 block,确认判断逻辑对了再打开 block。
## 交互式管理
[Section titled “交互式管理”](#交互式管理)
不想手写 JSON 的话:
```plaintext
/hooks
```
这个命令打开 hooks 的交互配置界面。它适合快速看当前有哪些 hook 在生效,复杂的条件逻辑还是直接写文件更清楚。
## 安全边界
[Section titled “安全边界”](#安全边界)
hooks 执行任意 shell 命令,权限等同于你自己。所以有几层限制机制。
个人层面,一键全关:
```json
{ "disableAllHooks": true }
```
这个开关同时关掉自定义 status line。
组织层面,managed 设置里有 `allowManagedHooksOnly`。开启后只加载三类 hook:managed 设置里的、SDK 传入的、以及在 managed 设置 `enabledPlugins` 里被强制启用的插件带的。用户 hook、项目 hook、其他插件的 hook 全部被拦。
这个设计的用意是让管理员能通过组织内部的插件市场分发经过审核的 hook,同时封掉其他来源。信任是按 `plugin@marketplace` 完整 ID 授予的——同名插件来自不同市场仍然被拦,避免了名字冒用。
HTTP 类型的 hook 有两个独立的允许列表:
* `allowedHttpHookUrls`:限制 HTTP hook 能请求哪些 URL,支持 `*` 通配。未定义时不限制,空数组表示全部禁止。主机名匹配大小写不敏感,并忽略末尾的 FQDN 点,与 DNS 语义一致。
* `httpHookAllowedEnvVars`:限制 HTTP hook 能把哪些环境变量插值进请求头。每个 hook 的有效列表是它自己声明的列表与这个设置的交集。
两个列表都跨设置层合并。
为什么为 HTTP hook 单独设计限制?
command 类型的 hook 跑在本机,它能读到什么取决于文件系统权限。HTTP hook 则是把数据发到网络的另一端,风险性质不同:一个能把任意环境变量插进请求头、又能请求任意 URL 的 hook,等于一条通用的凭据外泄通道。
所以这两个限制是成对的——只限 URL 不限变量,攻击者可以往允许的 URL 发凭据;只限变量不限 URL,仍然可以把其他敏感信息发到任意地址。
## 变式练习
[Section titled “变式练习”](#变式练习)
* 在 `PostToolUse` 上挂一个匹配 `Edit|Write` 的 hook,让它对刚改动的文件跑格式化工具。这是投入产出最高的一个 hook,因为它把「记得跑 formatter」这件事从你和模型双方的记忆里彻底移除了。
* 在 `SessionStart` 上挂一个输出 `additionalContext` 的 hook,把 `git log --oneline -5` 的结果注进去。观察模型在会话开头对「最近做了什么」的理解有没有变化。
* 故意写一个 `PreToolUse` hook 以退出码 2 退出,stderr 写一句具体的修正建议,看模型的下一步动作是不是照着建议改了。再换成退出码 1 对比一次。
## 还没确认的点
[Section titled “还没确认的点”](#还没确认的点)
* HTTP 类型 hook 的完整配置字段。官方文档主要在讲它的限制机制(`allowedHttpHookUrls`、`httpHookAllowedEnvVars`),配置项本身的完整字段列表没有逐条核实。
* 各事件 stdin 输入 JSON 的完整字段。公共字段(`session_id`、`transcript_path`、`cwd`、`hook_event_name`)确认了,各事件特有的字段没有逐个核实。
* 同一 matcher 组内多个 hook 并行执行时,如果多个 hook 同时返回冲突的 `permissionDecision`,最终采用哪个。文档未见明确说明。
* `ConfigChange` 事件的输入字段,以及它在一次批量配置改动中触发几次。
## 参考
[Section titled “参考”](#参考)
* [Hooks reference - Claude Code Docs](https://code.claude.com/docs/en/hooks)
* [Claude Code settings - Claude Code Docs](https://code.claude.com/docs/en/settings)
# Claude Code 接 MCP 服务器:三种 scope 存在哪、四种传输怎么选、工具搜索为什么默认开
> local / project / user 三种 scope 的存储位置与优先级、stdio / HTTP / SSE / WebSocket 的适用场景、.mcp.json 的环境变量展开语法,以及 tool search 如何避免上下文被工具定义吃满。
适用范围
适用工具:Claude Code CLI(v2.1.x 系列)。最后核对日期:2026-07-30。核实来源:官方 MCP 文档。前置假设:读过[配置优先级](/claude-code/settings-files/)。
## 结论
[Section titled “结论”](#结论)
判断该不该接 MCP 服务器的标准很具体:**你有没有在反复从另一个工具里复制数据粘贴进对话**。issue 描述、监控面板的报错、数据库查询结果——如果这些内容你每天都在手工搬运,那个系统就该接进来。反过来,为了「功能齐全」而接一堆用不上的服务器,只是在消耗启动时间。
三种 scope 的存储位置容易记混,先记住这张表:
| scope | 在哪些项目里加载 | 团队共享 | 存储位置 |
| --------- | -------- | -------- | ---------------- |
| local(默认) | 仅当前项目 | 否 | `~/.claude.json` |
| project | 仅当前项目 | 是,通过版本控制 | 项目根的 `.mcp.json` |
| user | 你的所有项目 | 否 | `~/.claude.json` |
踩坑预警
MCP 的「local scope」和设置文件的「local 层」是两个不同的东西,名字撞了。
* MCP local scope 存在 `~/.claude.json`(家目录)
* 设置的 local 层是 `.claude/settings.local.json`(项目目录)
两者没有关系。看到「local」时要先确认在说哪一个。
旧版本对 scope 的叫法不同:现在的 `local` 曾叫 `project`,现在的 `user` 曾叫 `global`。看老文章时注意这一层。
## 四种传输方式
[Section titled “四种传输方式”](#四种传输方式)
```bash
# HTTP:远程服务器的推荐选择
claude mcp add --transport http notion https://mcp.notion.com/mcp
# 带 Bearer token
claude mcp add --transport http secure-api https://api.example.com/mcp \
--header "Authorization: Bearer your-token"
# stdio:本地进程
claude mcp add --env AIRTABLE_API_KEY=YOUR_KEY --transport stdio airtable \
-- npx -y airtable-mcp-server
```
怎么选:
* **HTTP**:远程服务器的默认选择。支持 OAuth,云服务里支持面最广。
* **stdio**:本地进程,适合需要直接访问系统或跑自定义脚本的场景。
* **SSE**:已废弃,只有仅暴露 SSE 端点的服务还需要它。
* **WebSocket**:持久双向连接,适合服务器主动推事件的场景。不支持 OAuth,也不能用 `--transport` 标志添加,只能通过 `.mcp.json` 或 `claude mcp add-json` 配。
在 JSON 配置里,`type` 字段接受 `streamable-http` 作为 `http` 的别名。MCP 规范用的是前者这个名字,所以从服务端文档抄来的配置可以直接用。
### stdio 服务器的 `--` 是必须的
[Section titled “stdio 服务器的 -- 是必须的”](#stdio-服务器的----是必须的)
```bash
claude mcp add --env KEY=value --transport stdio myserver -- python server.py --port 8080
```
`--` 分隔 Claude 自己的选项(`--transport`、`--env`、`--scope`)和要运行的命令。`--` 之后的一切原样传给服务器。
没有 `--`,上面这条命令里的 `--port` 会被 Claude Code 当成自己的选项去解析然后报错。
`--env` 接受多个 `KEY=value` 对,所以服务器名不能紧跟在 `--env` 后面——CLI 会把名字读成另一个键值对然后拒绝。至少在 `--env` 和服务器名之间放一个其他选项。
## 一个常见的配置错误
[Section titled “一个常见的配置错误”](#一个常见的配置错误)
JSON 配置里写了 `url` 但没写 `type`,是配置错误。Claude Code 把没有 `type` 的条目当作 stdio 服务器,于是报:
```plaintext
MCP server "" has a "url" but no "type"; add "type": "http" (or "sse" / "ws") to this entry
```
v2.1.202 之前这个错误的措辞是 `command: expected string, received undefined`,看起来完全不像是缺 `type` 导致的。搜到旧报错信息时,答案是加 `type` 字段。
## project scope 与信任边界
[Section titled “project scope 与信任边界”](#project-scope-与信任边界)
project scope 的服务器写在项目根的 `.mcp.json` 里,提交进版本控制,团队每个人拿到同一套工具:
```json
{
"mcpServers": {
"shared-server": {
"type": "http",
"url": "https://example.com/mcp"
}
}
}
```
出于安全考虑,使用 `.mcp.json` 里的服务器之前 Claude Code 会要求你批准。要重置批准记录:
```bash
claude mcp reset-project-choices
```
这里有一层容易忽略的防御。v2.1.196 起,在你信任这个 workspace 之前(跑一次 `claude` 并接受信任对话框),`claude mcp list` 和 `claude mcp get` 只从**没有提交进仓库的**设置文件读取 `.mcp.json` 的批准记录。
意思是:一个克隆下来的仓库不能自己批准自己的服务器。提交在项目 `.claude/settings.json` 里的 `enableAllProjectMcpServers` 或 `enabledMcpjsonServers` 在未信任的文件夹里被忽略,服务器停在 `⏸ Pending approval` 状态。
在未信任的文件夹里仍然生效的批准来源:
* 你的 `~/.claude/settings.json`
* managed 设置
* 通过 `--settings` 传入的设置
`disabledMcpjsonServers` 里的条目在任何设置文件里都能拒绝服务器——拒绝方向不受信任门槛限制。
为什么批准和拒绝不对称?
这是安全机制的一个通用原则:放行需要授权,拦截不需要。
如果拒绝规则也要等信任,那么「我不想让任何仓库连这个服务器」这条意图在未信任的仓库里反而失效了——正好是最需要它生效的场合。所以拒绝方向一律立即生效。
## .mcp.json 的环境变量展开
[Section titled “.mcp.json 的环境变量展开”](#mcpjson-的环境变量展开)
团队共享配置时,机器相关的路径和密钥不能写死。`.mcp.json` 支持两种展开语法:
* `${VAR}`:展开为环境变量的值
* `${VAR:-default}`:有值用值,无值用默认
可展开的位置:`command`、`args`、`env`、`url`、`headers`。
```json
{
"mcpServers": {
"api-server": {
"type": "http",
"url": "${API_BASE_URL:-https://api.example.com}/mcp",
"headers": {
"Authorization": "Bearer ${API_KEY}"
}
}
}
}
```
变量未设置且没有默认值时,配置仍然加载:Claude Code 在 `claude mcp list` 输出里为该服务器报一个缺变量警告,并把未展开的 `${VAR}` 文本原样使用。所以服务器连不上、报的是认证错误时,先检查 `claude mcp list` 有没有这个警告——把字面量 `${API_KEY}` 当 token 发出去,服务端返回的当然是 401。
`CLAUDE_PROJECT_DIR` 是一个特例。Claude Code 会把它设进 stdio 服务器进程的环境里,但它不在 Claude Code 自己的环境里。所以在 `.mcp.json` 的 `command` 或 `args` 里用 `${CLAUDE_PROJECT_DIR}` 展开需要带默认值:`${CLAUDE_PROJECT_DIR:-.}`。插件提供的 MCP 配置直接替换这个占位符,不需要默认值。
## tool search:为什么接很多服务器也不会撑爆上下文
[Section titled “tool search:为什么接很多服务器也不会撑爆上下文”](#tool-search为什么接很多服务器也不会撑爆上下文)
tool search 默认开启。它的作用是把工具定义**延迟加载**:会话开始时只加载工具名和服务器说明,Claude 需要用某个工具时通过一个搜索工具去发现它。只有实际用到的工具进入上下文。
这解决了一个真实问题:每个 MCP 服务器的完整工具定义(参数 schema、描述)都很占 token。接五六个服务器,光工具定义就能吃掉可观的上下文。
从使用体验上你感觉不到区别。Claude Code 也因此不设固定的每服务器工具数上限,实际限制是上下文预算。
`ENABLE_TOOL_SEARCH` 的取值:
| 值 | 行为 |
| -------- | ----------------------------------- |
| 未设置 | 全部延迟加载(几个特定部署环境下回退为前置加载) |
| `true` | 全部延迟加载 |
| `auto` | 阈值模式:工具定义能装进上下文窗口的 10% 就前置加载,超出部分延迟 |
| `auto:N` | 阈值模式,自定义百分比,N 取 0-100 |
| `false` | 全部前置加载 |
tool search 需要支持 `tool_reference` 块的模型:Claude Sonnet 4.5、Haiku 4.5、Opus 4.5 及更新的模型。
有少数工具是每轮都要用的,让它们走搜索反而多一次往返。给那个服务器加 `alwaysLoad`:
```json
{
"mcpServers": {
"core-tools": {
"type": "http",
"url": "https://mcp.example.com/mcp",
"alwaysLoad": true
}
}
}
```
代价要知道:`alwaysLoad: true` 会让启动阻塞到该服务器连上(上限是 5 秒的标准连接超时)。MCP 启动本来是非阻塞的,但这些工具必须在构建第一个 prompt 时就在场。其他服务器继续在后台连接。
## 工具命名与权限规则
[Section titled “工具命名与权限规则”](#工具命名与权限规则)
MCP 工具的可调用名是 `mcp____`。在权限规则、skill 的 `allowed-tools`、subagent 的 `tools` 字段、hook matcher 里引用工具时用这个全名。
插件捆绑的 MCP 服务器命名不同,是 `mcp__plugin____`:
```plaintext
mcp__plugin_my-plugin_database-tools__query
```
这一点会造成一个隐蔽的失效:针对裸服务器名写的 hook matcher,比如 `mcp__database-tools__.*`,对插件捆绑的服务器**永远不会触发**。hook 没反应时,先确认这个服务器是不是来自插件。
服务器本身注册的名字是 `plugin::`,在需要「配置的服务器名」的地方(比如 `mcp_tool` hook 的 `server` 字段)用这个。
有一批服务器名是内置保留的:`workspace`、`claude-in-chrome`、`computer-use`、`Claude Preview`、`Claude Browser`。配置里用了保留名,Claude Code 加载时跳过并警告;`claude mcp add` 会直接报错。
## 输出限制
[Section titled “输出限制”](#输出限制)
MCP 工具输出超过 10,000 token 时 Claude Code 给警告,默认上限 25,000 token。上限可调:
```bash
export MAX_MCP_OUTPUT_TOKENS=50000
```
警告阈值固定,不可调。
经常撞警告的服务器,如果不是你维护的,可以请作者加 `anthropic/maxResultSizeChars` 标注或做分页。这个标注对返回图片数据的工具无效——那种情况只能调 `MAX_MCP_OUTPUT_TOKENS`。
## 排错顺序
[Section titled “排错顺序”](#排错顺序)
服务器连不上时,按这个顺序查:
1. `claude mcp list` 看健康状态。`✔ Connected` / `! Needs authentication` / `✘ Failed to connect` / `⏸ Pending approval` 四种状态指向完全不同的原因。注意 WebSocket 服务器不出现在这个列表里,用 `claude mcp get ` 或 `/mcp` 面板查。
2. 同一个输出里看有没有缺环境变量的警告。
3. `⏸ Pending approval` 说明是信任问题,交互式跑一次 `claude` 批准。
4. `! Needs authentication` 用 `/mcp` 或 `claude mcp login ` 走 OAuth。
5. `✘ Failed to connect` 且配了 `headers.Authorization`:如果服务端拒绝这个头,Claude Code 报连接失败,**不会回退到 OAuth**。确认 token 对这个端点有效,或者干脆删掉这个头改用 OAuth 流程。
`claude mcp add` 保存配置时不校验凭据,所以占位符值会被接受,问题在之后连接时才暴露。加完总是用 `/mcp` 确认一次。
## 变式练习
[Section titled “变式练习”](#变式练习)
* 挑一个你每天都在手工复制内容的系统(issue 追踪、监控面板),接进来,然后连续用三天。三天后判断它是真的省事了还是只是看起来酷。
* 在 `.mcp.json` 里故意写一个未定义的 `${SOME_VAR}`,跑 `claude mcp list` 看警告长什么样。这个警告一旦见过一次,以后遇到莫名的 401 就会先想到它。
* 用 `ENABLE_TOOL_SEARCH=false` 启动一次,用 `/context` 对比上下文占用。这能让你对工具定义的 token 成本有具体数字感。
## 还没确认的点
[Section titled “还没确认的点”](#还没确认的点)
* `WaitForMcpServers` 工具的具体行为。文档提到在没有 tool search 的配置里 Claude 用它等待服务器连接,细节没有核实。
* channels 机制(服务器主动推消息进会话)的完整配置流程。本文只提到它的存在。
* 自动后台化的边界情况。主对话里超过两分钟的 MCP 调用会转为后台任务,但 subagent 的调用、IDE 服务器的调用不会——这些例外背后的完整规则没有逐条核实。
* managed MCP 配置(`managed-mcp.json`、`allowedMcpServers`)的部署细节。
## 参考
[Section titled “参考”](#参考)
* [Connect Claude Code to tools via MCP - Claude Code Docs](https://code.claude.com/docs/en/mcp)
* [Claude Code settings - Claude Code Docs](https://code.claude.com/docs/en/settings)
# Claude Code 六种权限模式:auto mode 到底和 bypassPermissions 差在哪
> default / acceptEdits / plan / auto / dontAsk / bypassPermissions 六种模式的差别,auto mode 的分类器机制与自定义规则,以及为什么项目配置无法给自己授予 auto mode。
适用范围
适用工具:Claude Code CLI(v2.1.x 系列)。最后核对日期:2026-07-30。核实来源:官方 permission-modes 与 settings 文档。前置假设:读过[配置优先级](/claude-code/settings-files/)。
## 结论
[Section titled “结论”](#结论)
想让 agent 挂着跑很久不被打断,正确的选择通常是 auto mode,不是 `--dangerously-skip-permissions`。
两者的区别不是「宽松程度不同」,是机制不同:auto mode 有一个分类器(classifier)在逐条判断每个操作的风险,判断结果分四档处理;bypassPermissions 是把整个权限判断层关掉。前者仍然存在硬边界,后者没有边界。
## 六种模式
[Section titled “六种模式”](#六种模式)
| 模式 | 行为 |
| -------------------------- | ---------------------- |
| `default`(CLI 里显示为 Manual) | 每个需要权限的操作都问你 |
| `acceptEdits` | 文件编辑自动通过,其他操作照常问 |
| `plan` | 只读探索与方案撰写,不执行改动 |
| `auto` | 分类器逐条判断,低风险自动放行,高风险仍然拦 |
| `dontAsk` | 不弹提示,被拦的操作直接失败而非询问 |
| `bypassPermissions` | 不做权限判断 |
切换方式有两种:会话内按 `Shift+Tab` 循环,或启动时用 `--permission-mode `。也可以在 `settings.json` 里写 `permissions.defaultMode` 设默认值。
`manual` 是 `default` 的别名,需要 v2.1.200 或更高版本。
## auto mode 的分类器
[Section titled “auto mode 的分类器”](#auto-mode-的分类器)
auto mode 的核心是一个分类器,它读取即将执行的命令,判断它属于哪一档。配置项是 `autoMode`,四个数组:
| 数组 | 含义 |
| ------------- | ---------------------- |
| `environment` | 描述运行环境的散文规则,给分类器提供判断背景 |
| `allow` | 明确放行 |
| `soft_deny` | 拦下来但可以问你 |
| `hard_deny` | 直接拒绝 |
这些规则是**散文(prose)**,不是模式匹配。写法像这样:
```json
{
"autoMode": {
"soft_deny": ["$defaults", "Never run terraform apply"],
"environment": ["This is a staging environment, data loss is recoverable"]
}
}
```
字面字符串 `"$defaults"` 表示在该位置继承内置规则。不写 `$defaults` 就是完全替换掉内置规则——这几乎总不是你想要的,因为内置规则里包含了一批基础的危险操作识别。
为什么用散文而不是命令模式匹配?
`permissions.deny` 里的 `Bash(curl *)` 这类模式匹配有一个根本限制:命令的表达方式太多。`curl` 可以写成 `/usr/bin/curl`、可以通过 `sh -c` 包一层、可以先 `alias`、可以用 `xargs` 转发。要用模式穷尽所有等价写法是做不到的。
分类器读的是命令的**意图**,所以 `Never run terraform apply` 这条规则不依赖命令的字面形式。代价是它的判断不是确定性的——同一条命令在不同上下文下可能得到不同结论。
这两种机制因此是互补关系而非替代关系:`permissions.deny` 提供确定性的硬边界,分类器提供覆盖面。两者一起用。
`environment` 数组容易被忽略但很有用。分类器判断风险时需要背景:在一次性的容器里 `rm -rf` 某个目录和在生产机上做同样的事,风险等级完全不同。把环境性质写进 `environment`,分类器的判断会更贴合实际。
## 一个默认行为:shell 命令的 allow 规则会被绕过吗
[Section titled “一个默认行为:shell 命令的 allow 规则会被绕过吗”](#一个默认行为shell-命令的-allow-规则会被绕过吗)
`autoMode.classifyAllShell` 默认为 `false`。这个默认值的含义需要拆开说。
auto mode 激活时,Claude Code 会挂起那些「匹配到任意代码执行模式」的 Bash / PowerShell allow 规则,让它们走分类器。但不匹配这类模式的 allow 规则仍然直接生效。
把 `classifyAllShell` 设为 `true` 则挂起**全部** Bash 和 PowerShell allow 规则,所有 shell 命令一律经过分类器。需要 v2.1.193 或更高版本。
什么时候需要开:你的 `permissions.allow` 里有一批看起来安全的具体命令(比如 `Bash(npm run build)`),但你不确定这些命令内部会不会做别的事情。开启后这些命令也会被分类器过一遍。
## 为什么项目配置不能给自己开 auto mode
[Section titled “为什么项目配置不能给自己开 auto mode”](#为什么项目配置不能给自己开-auto-mode)
在 `.claude/settings.json` 或 `.claude/settings.local.json` 里写 `"defaultMode": "auto"` 不生效,也不报错。
原因是威胁模型:如果项目层配置能设 auto mode,那么克隆任意一个仓库、在里面启动 Claude Code,这个仓库就自动获得了「大部分操作不问你」的权限。仓库里的 CLAUDE.md、hook 脚本、MCP 配置都是仓库作者写的,等于把决策权交给了一个你还没审查过的代码库。
所以 `auto` 这个值只从 `~/.claude/settings.json`、`--settings` 标志和 managed 设置读取。v2.1.142 之前项目设置可以设,是被收紧的。
`autoMode` 的分类器规则同理,只从用户层、`--settings`、managed 层读。否则仓库可以往自己的 `allow` 里加规则,等价于自我提权。v2.1.207 之前 `.claude/settings.local.json` 也在读取范围内。
## plan 模式与 auto mode 的交叉
[Section titled “plan 模式与 auto mode 的交叉”](#plan-模式与-auto-mode-的交叉)
`useAutoModeDuringPlan` 默认为 `true`:auto mode 可用时,plan 模式会采用 auto mode 语义。
这个设计的用意是让方案撰写阶段的探索更顺畅——plan 模式本来就不改文件,探索性的只读命令(看目录结构、读配置、查 git 历史)不需要逐条确认。
这个字段不从共享的项目设置读取,同样是防止仓库影响你的权限行为。
## 关掉这两个模式
[Section titled “关掉这两个模式”](#关掉这两个模式)
组织侧或个人侧都可以禁用:
```json
{
"disableAutoMode": "disable",
"permissions": {
"disableBypassPermissionsMode": "disable"
}
}
```
`disableAutoMode` 会把 `auto` 从 `Shift+Tab` 循环里移除,并在启动时拒绝 `--permission-mode auto`。
`disableBypassPermissionsMode` 同时禁用 `--dangerously-skip-permissions` 命令行标志。这两个字段在任何作用域都能用,但典型位置是 managed 设置。
还有一个相关字段 `skipDangerousModePermissionPrompt`,跳过进入 bypass 模式前的确认提示。它在项目设置里被忽略——防止一个不受信任的仓库让你在无感知的情况下进入 bypass 模式。
## 权限规则的判定顺序
[Section titled “权限规则的判定顺序”](#权限规则的判定顺序)
权限规则的格式是 `Tool` 或 `Tool(specifier)`,判定顺序是 deny → ask → allow,**第一个匹配的规则决定结果,与规则的具体程度无关**。
最后半句是最容易踩的地方。写了一条宽泛的 `deny` 和一条精确的 `allow`,精确的那条不会因为「更具体」而胜出——deny 先被检查,匹配上就结束了。
几个例子:
| 规则 | 匹配 |
| ------------------------------ | ----------------- |
| `Bash` | 所有 Bash 命令 |
| `Bash(npm run *)` | 以 `npm run` 开头的命令 |
| `Read(./.env)` | 读 `.env` 文件 |
| `WebFetch(domain:example.com)` | 请求 example.com |
工具名通配只在 `mcp____` 这个字面前缀之后的工具位置支持,比如 `mcp__github__get_*`;server 段本身不能带通配。deny 规则里工具名可以用通配:`*` 拒绝全部工具,`mcp__*` 拒绝全部 MCP 工具。
deny 规则不能在其他工具仍然可用的情况下移除 `EndConversation`。
## 怎么选
[Section titled “怎么选”](#怎么选)
按「出错后果的可逆性」选,不按「想少点几次确认」选:
* 代码在 git 里、改动可回滚、命令不碰外部系统 → auto mode 够用,是长时间挂机的默认选择
* 只想让文件编辑免确认,其他照常 → `acceptEdits`
* 一次性容器、跑完即弃、没有任何持久化后果 → 才考虑 bypassPermissions
* 生产环境凭据在环境变量里、有能力调用外部 API → 不要用 bypassPermissions,用 auto mode 配 `hard_deny` 明确列出禁止操作
`dontAsk` 是一个容易被误选的模式。它不弹提示,但被拦的操作是**直接失败**而不是询问。这在无人值守的脚本里是合理的(没人能回答提示),在交互式使用里通常不是你想要的。
## 变式练习
[Section titled “变式练习”](#变式练习)
* 在 `autoMode.hard_deny` 里加一条你所在项目最不能容忍的操作(比如 `Never modify files under migrations/`),然后让模型尝试做这件事,看拦截信息是什么样的。
* 对比 auto mode 和 `acceptEdits` 在同一个任务上的确认次数。这能让你对分类器实际放行的范围有直观感受。
* 写一条宽泛的 `permissions.deny` 和一条更精确的 `allow`,验证 deny 确实先胜出,加深对「第一个匹配决定结果」的印象。
## 还没确认的点
[Section titled “还没确认的点”](#还没确认的点)
* auto mode 的账户与版本要求。文档提到它对账户类型有要求,具体哪些订阅层级可用没有核实清楚。
* 分类器判断在多大程度上是确定性的。同一条命令在同一配置下是否总得到相同结论,文档未见说明。
* 分类器与 sandbox 的具体交互。两者都在限制命令行为,谁先生效、判断结果如何叠加,没有查到明确描述。
* `soft_deny` 被触发时呈现给用户的确认界面与 default 模式的权限提示是否相同。
## 参考
[Section titled “参考”](#参考)
* [Permission modes - Claude Code Docs](https://code.claude.com/docs/en/permission-modes)
* [Claude Code settings - Claude Code Docs](https://code.claude.com/docs/en/settings)
# Claude Code 的四层配置:settings.json 放什么、.claude.json 放什么、谁覆盖谁
> managed / 命令行 / local / project / user 五级优先级的完整顺序,标量覆盖与数组合并的区别,以及 settings.json 和 .claude.json 的职责边界。
适用范围
适用工具:Claude Code CLI(v2.1.x 系列)。最后核对日期:2026-07-30。核实来源:官方 settings 文档。前置假设:已装好 Claude Code,至少改过一次配置。
## 结论
[Section titled “结论”](#结论)
配置文件有四个作用域,加上命令行参数一共五级。从高到低:managed(企业管理策略)、命令行参数、`.claude/settings.local.json`、`.claude/settings.json`、`~/.claude/settings.json`。managed 层压过一切,包括命令行参数。
标量值(字符串、布尔、数字)按这个顺序覆盖;数组值(如 `permissions.allow`)跨层拼接去重,不覆盖。这个区别是配置调试里最容易踩的一处:以为项目层的 deny 规则会替换用户层的,实际是两边的规则都在生效。
`settings.json` 和 `.claude.json` 是两个不同的文件,职责不重叠。往错的那个文件里写 key,Claude Code 启动时静默忽略,不报错。
## 五级优先级,以及为什么 managed 压过命令行
[Section titled “五级优先级,以及为什么 managed 压过命令行”](#五级优先级以及为什么-managed-压过命令行)
优先级从高到低:
| 级别 | 位置 | 谁能改 |
| ------- | ------------------------------------------------- | ------------- |
| managed | 服务端下发 / MDM 策略 / 系统目录下的 `managed-settings.json` | IT 管理员 |
| 命令行参数 | `--settings `、`--permission-mode` 等 | 当前这次会话 |
| local | 仓库根的 `.claude/settings.local.json` | 你自己,仅这个仓库 |
| project | `.claude/settings.json` | 提交进 git,全团队共享 |
| user | `~/.claude/settings.json` | 你自己,所有项目 |
命令行参数通常是优先级最高的,这里不是。managed 层的定位是「组织策略不可绕过」,所以它必须压过 `--settings`,否则任何人加一个命令行参数就能解掉安全策略。
managed 层在不同平台的落地位置不同:
* macOS:`/Library/Application Support/ClaudeCode/managed-settings.json`
* Linux 和 WSL:`/etc/claude-code/managed-settings.json`
* Windows:`C:\Program Files\ClaudeCode\managed-settings.json`
Windows 上的旧路径 `C:\ProgramData\ClaudeCode\managed-settings.json` 自 v2.1.75 起不再被读取。如果之前部署在那里,需要迁移。
为什么是这样?
managed 层还有一个和其他层不同的性质:解析是「容错」的。某个 managed 条目 schema 校验失败时,Claude Code 只剥掉那一条,记一个警告,其余策略继续生效。用户层、项目层、local 层则是「严格」的:文件里有一处校验失败,整个文件被整体拒绝。
差别的原因在于失败后果不对称。企业策略里一个 typo 如果导致整份策略失效,等于一个错字关掉了全公司的安全基线。个人配置文件校验失败则只影响自己,整体拒绝并报错反而更容易发现问题。
## 标量覆盖,数组合并
[Section titled “标量覆盖,数组合并”](#标量覆盖数组合并)
这是配置调试里最常被误解的一条。
标量值按优先级覆盖。用户设置里 `spinnerTipsEnabled` 是 `true`、项目设置里是 `false`,最终生效的是 `false`(项目层优先级更高)。
数组值跨层拼接去重。managed 层把 `sandbox.filesystem.allowWrite` 设成 `["/opt/company-tools"]`,你在用户层加了 `["~/.kube"]`,最终两个路径都在。低优先级的层可以往数组里添加条目,不会被高优先级层清空。
权限规则也走合并这条路。所以「项目层写了 `permissions.deny`,就能覆盖掉用户层的 deny」这个预期是错的:两边的 deny 规则都在生效,实际拦截范围是并集。
两个数组字段是例外,不参与合并:
* `fallbackModel`:这是一条有顺序含义的链,位置本身携带信息。定义它的最高优先级文件提供整条链的值。
* `availableModels`:最高优先级的 managed 源定义它时,那份列表原样生效,用户 / 项目 / local 层的条目无法扩展它。非 managed 的几层之间照常合并。
## settings.json 与 .claude.json 的分工
[Section titled “settings.json 与 .claude.json 的分工”](#settingsjson-与-claudejson-的分工)
`settings.json` 是行为配置文件:`env`、`permissions`、`hooks`、`model`、`statusLine` 这些都写在这里。
`~/.claude.json` 是 CLI 自身维护的状态文件,内容包括:
* OAuth 会话(登录态)
* MCP 服务器的 local scope 和 user scope 配置
* 每个项目的状态:已批准的工具、workspace 信任记录
* 各类缓存
project scope 的 MCP 服务器不在这里,单独放在项目根的 `.mcp.json`。
有一组 key 只认 `~/.claude.json`,写进 `settings.json` 会被静默忽略:
| key | 作用 |
| ---------------------------- | --------------------------------------------------------- |
| `autoConnectIde` | 从外部终端启动时是否自动连接正在运行的 IDE |
| `autoInstallIdeExtension` | 在 VS Code 终端里运行时是否自动装 IDE 扩展 |
| `diffTool` | 连了 IDE 时 diff 显示在哪:`auto` 走 IDE 的 diff 视图,`terminal` 留在终端 |
| `externalEditorContext` | 用 Ctrl+G 打开外部编辑器时,是否把上一条回复作为 `#` 注释带进去 |
| `permissionExplainerEnabled` | Bash 权限提示上按 Ctrl+E 时是否给出命令解释 |
| `teammateDefaultModel` | agent team 里 teammate 的默认模型 |
v2.1.119 之前,一批 `/config` 偏好项也存在 `~/.claude.json` 而非 `settings.json` 里,包括 `theme`、`verbose`、`editorMode`、`autoCompactEnabled`、`preferredNotifChannel`。跨版本抄配置片段时这是一个容易踩的差异点。
## 改完配置怎么确认它生效了
[Section titled “改完配置怎么确认它生效了”](#改完配置怎么确认它生效了)
配置改完不要靠「行为看起来对了」来判断,因为同一个行为可能由别的层决定。跑一次会话后执行:
```plaintext
/status
```
Status 标签页里有一行 `Setting sources`,列出当前会话加载的每一层来源,例如 `User settings`、`Project local settings`。managed 层会额外标出下发渠道,形如 `Enterprise managed settings (remote)`、`(plist)`、`(HKLM)`、`(HKCU)`、`(file)`。
这一行有两个限制要知道:
* 只有「已加载且至少含一个 key」的来源才会出现。JSON 语法坏掉的文件不出现在列表里,即使它有内容。所以某一层没出现,第一件事是查那个文件的 JSON 是否合法。
* 它只告诉你哪些来源被读了,不告诉你某一个具体 key 最终由哪一层提供。
要看每一条错误的细节,用:
```bash
claude doctor
```
## 大多数 key 改完立即生效,两个例外
[Section titled “大多数 key 改完立即生效,两个例外”](#大多数-key-改完立即生效两个例外)
Claude Code 监听设置文件,改动后会重载,多数 key 不需要重启会话。`permissions`、`hooks`、`apiKeyHelper` 这些都在重载范围内,user / project / local / managed 四层都监听。每检测到一次改动,`ConfigChange` hook 会触发一次。
两个 key 是启动时读一次的:
* `model`:会话中途要换模型用 `/model`
* `outputStyle`:它属于系统提示的一部分,系统提示只在 `/clear` 或重启时重建
## 一个容易误判的场景:defaultMode: auto 不生效
[Section titled “一个容易误判的场景:defaultMode: auto 不生效”](#一个容易误判的场景defaultmode-auto-不生效)
在 `.claude/settings.json` 或 `.claude/settings.local.json` 里写 `"permissions": {"defaultMode": "auto"}`,会话仍然以 default 模式启动,而且不报错。
这不是 bug。Claude Code v2.1.142 及以后版本会忽略来自项目层和 local 层的 `auto` 值,避免一个仓库通过提交配置文件的方式给自己授予 auto mode。要让它生效,把这一项移到 `~/.claude/settings.json`。
同类的「按来源忽略」规则还有几处,都是同一个防御思路,防止克隆下来的仓库通过自带配置提权:
| 字段 | 忽略哪些来源 |
| ----------------------------------- | -------- |
| `permissions.defaultMode: "auto"` | 项目、local |
| `autoMode`(分类器规则) | 项目、local |
| `pluginConfigs` | 项目、local |
| `footerLinksRegexes` | 项目、local |
| `vimInsertModeRemaps` | 项目、local |
| `skipDangerousModePermissionPrompt` | 项目 |
| `enableArtifact` | 项目、local |
判断方法一致:这个字段一旦被仓库自带的配置控制,会不会让仓库获得它本不该有的权限或对你的输入 / 凭据的影响力。会,就只从用户层和 managed 层读。
踩坑预警
`.claude/settings.local.json` 的位置在 v2.1.211 有过一次变化。之前它总是在「启动 Claude Code 的那个目录」;现在 Claude Code 会在 git 仓库根读写这个文件,并且通过 worktree 解析到主 checkout,让一份文件覆盖仓库任意子目录或 worktree 里启动的会话。
旧版本留在启动目录的那份文件仍然会被读取。两份文件设了同一个 key 时,仓库根的值胜出,但两份文件里的权限规则都保持生效(数组合并那条规则)。所以升级后如果发现权限比预期宽,检查一下是不是有两份 `settings.local.json`。
三种情况下这个文件仍然留在启动目录:不在 git 仓库里、仓库根就是你的家目录、Agent SDK 会话。
## 让编辑器帮你校验
[Section titled “让编辑器帮你校验”](#让编辑器帮你校验)
在 `settings.json` 顶部加一行 `$schema`,VS Code、Cursor 这类支持 JSON schema 的编辑器就能给出补全和内联校验:
```json
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"permissions": {
"allow": ["Bash(npm run lint)"],
"deny": ["Read(./.env)", "Read(./secrets/**)"]
}
}
```
这份公开 schema 是定期更新的,可能不包含最近几个版本新加的字段。所以一个刚出现在文档里的字段被编辑器标黄,不代表配置真的无效,对照官方文档确认即可。
## 变式练习
[Section titled “变式练习”](#变式练习)
* 在用户层和项目层各写一条 `permissions.deny`,然后跑 `/permissions` 看列表里是不是两条都在。这能直接验证「数组合并」而不是覆盖。
* 故意把 `.claude/settings.json` 的 JSON 写坏(少一个逗号),看 `/status` 的 `Setting sources` 里这一层会不会消失,再用 `claude doctor` 看它报的是什么。
## 还没确认的点
[Section titled “还没确认的点”](#还没确认的点)
* `~/.claude.json` 的完整字段结构。官方文档说明了它的角色和几个 key,没有逐条列出全部字段。本文列的 key 来自官方 settings 文档的 Global config settings 一节,不代表这就是全部。
* 配置文件的自动备份机制。文档提到 Claude Code 会创建带时间戳的备份并保留最近五份,具体的备份文件命名和位置没有查到明确说明。
* 各版本之间 key 归属的迁移细节。只核实到 v2.1.119 这一次变化,更早的版本没有跟踪。
## 参考
[Section titled “参考”](#参考)
* [Claude Code settings - Claude Code Docs](https://code.claude.com/docs/en/settings)
* [Permission modes - Claude Code Docs](https://code.claude.com/docs/en/permission-modes)
* [Hooks reference - Claude Code Docs](https://code.claude.com/docs/en/hooks)
# 前言:这个站是干什么的
> 这个站收集什么、写给谁、每篇怎么写,以及它和《验收先行》的关系。
有一批内容没地方放:Claude Code 接入国内可用模型后端、上下文自动压缩的阈值、定时循环跑的 agent、24 小时长任务。它们具体到字段名和报错原文,塞进[《验收先行》](https://ai-coding-from-zero.pages.dev)会撑破那本零基础教程,放在仓库内部文档里又没人读。这个站是给它们找的位置:用书的骨架,装博客式的内容。
## 这个站做一件事
[Section titled “这个站做一件事”](#这个站做一件事)
给愿意碰命令行、能自己查文档的人,提供当下可复现的 AI 编程工具配置与 agent 工程细节。具体到字段名、命令、报错原文,以及「怎么确认它生效了」的验证方法。
几件事这里不做。不做零基础教学,不铺垫,默认读者会开终端、会查文档。不追求内容长期有效:工具变得快,每篇顶部标「最后核对日期」,过时的内容不删,在原位置加更新说明,让读者自己判断「什么时候变的」。不限定工具链:Claude Code、Codex、其他工具和模型,碰到什么写什么——目前写得最多的是 Claude Code,因为它的配置面最大、变得也最快。
## 每篇怎么写
[Section titled “每篇怎么写”](#每篇怎么写)
结构固定为四段:结论先给,然后是完整的复现步骤(配置片段整段给出,不只给结论),再跟一段「怎么确认它生效了」,最后标核对日期和版本。允许从命令开始读,没有动机铺垫。
内容依据官方文档和本机实测,不依据记忆或转述。AI 参与检索、核对和整理,未经核实的内容不进正文,留在每篇末尾的「还没确认的点」里——这些条目在[全站待核实清单](/reference/unverified/)有汇总,按「文档未覆盖 / 需要实测 / 版本差异」分类。
## 不用从头读到尾
[Section titled “不用从头读到尾”](#不用从头读到尾)
每篇独立成立,分支之间没有依赖。搜报错进来的读者直接跳对应段落,或者去[报错原文索引](/reference/error-index/)按原文查;按分支逛的读者从侧栏进,顺序随意。
没有想法的话,建议起点是[CLAUDE.md 上下文工程](/agents/claude-md-context/)——它解释了 agent 到底读到了什么,后面几篇都建立在这个基础上。
# 速查与索引
> 带着报错原文进来的话,从这里查。这一部不讲原理,只做定位与索引。
[报错原文索引 ](./error-index/)按报错信息原文查,每条给出所在章节与定位方向
[待核实清单 ](./unverified/)全站所有「还没确认的点」的汇总,以及它们各自的不确定程度
# 报错原文索引
> 按报错信息原文查定位方向,覆盖区域限制、MCP 连接、subagent 限额、配置不生效等本站已核实的报错。
这一页怎么用
按 `Ctrl+F` 搜你手上的报错原文。找不到的话,本站可能还没覆盖到——去[待核实清单](/reference/unverified/)看看是不是列在那里。最后更新:2026-07-30。
## 认证与区域
[Section titled “认证与区域”](#认证与区域)
**`unsupported_country_region_territory`**
真实原因不是「你在的地区不能用」这么简单。详见 [接入 DeepSeek 后端](/claude-code/deepseek-backend/)。
## MCP 服务器
[Section titled “MCP 服务器”](#mcp-服务器)
**`MCP server "" has a "url" but no "type"; add "type": "http" (or "sse" / "ws") to this entry`**
JSON 配置里写了 `url` 但没写 `type`。Claude Code 把没有 `type` 的条目当 stdio 服务器处理。加上 `type` 字段即可。
**`command: expected string, received undefined`**
这是上一条报错在 v2.1.202 之前的措辞。看起来像是 `command` 字段的问题,实际是缺 `type`。
**`✘ Failed to connect`**(`claude mcp list` 状态)
配了 `headers.Authorization` 而服务端拒绝这个头时,Claude Code 报连接失败且**不会回退到 OAuth**。也可能是环境变量没展开——同一个输出里会有缺变量警告,把字面量 `${API_KEY}` 当 token 发出去,服务端返回 401。
**`⏸ Pending approval`**(`claude mcp list` 状态)
`.mcp.json` 里的服务器需要你批准。交互式跑一次 `claude` 接受信任对话框。提交在项目 `.claude/settings.json` 里的 `enableAllProjectMcpServers` 在未信任的文件夹里被忽略。
**`! Needs authentication`**(`claude mcp list` 状态)
用 `/mcp` 面板或 `claude mcp login ` 走 OAuth。
以上全部详见 [接 MCP 服务器](/claude-code/mcp-servers/)。
## subagent
[Section titled “subagent”](#subagent)
**`Subagent spawn limit reached`**
会话累计 subagent 数达到上限(默认 200,含已完成的)。`/clear` 重置计数,或调 `CLAUDE_CODE_MAX_SUBAGENTS_PER_SESSION`。
**`Concurrent subagent limit reached`**
同时运行的 subagent 数达到上限(默认 20)。等运行数降下来,或调 `CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS`。
这两个是**不同的限额**,错误信息相似但对应的变量不同。详见 [subagent](/agents/subagents/)。
## 预算与上限
[Section titled “预算与上限”](#预算与上限)
**`Budget limit reached`**
`--max-budget-usd` 的上限到了。subagent 的花费计入这个上限,且仍在运行的后台 subagent 会被停止。详见 [放进脚本和 CI](/claude-code/headless-ci/)。
## 命令拼错
[Section titled “命令拼错”](#命令拼错)
**`Did you mean claude update?`**
子命令拼错时 Claude Code 建议最接近的匹配并退出,不启动会话。
## 静默失败:不报错但也不生效
[Section titled “静默失败:不报错但也不生效”](#静默失败不报错但也不生效)
这一类比报错更难查,因为没有任何输出提示你出了问题。
| 现象 | 原因 |
| -------------------------------------- | ------------------------------------------------------------------------------------------------- |
| `settings.json` 里某个 key 完全没作用 | 那个 key 只认 `~/.claude.json`(如 `diffTool`、`autoConnectIde`)。见[配置优先级](/claude-code/settings-files/) |
| `"defaultMode": "auto"` 不生效且不报错 | 项目层和 local 层的 `auto` 值被有意忽略,要写在 `~/.claude/settings.json`。见[权限模式](/claude-code/permission-modes/) |
| 某一层设置整个没加载 | 那个文件 JSON 语法坏了。`/status` 的 `Setting sources` 里不会出现它,用 `claude doctor` 看解析错误 |
| 项目层 deny 规则没「覆盖」用户层 | 数组值是跨层合并的,不是覆盖。实际拦截范围是并集 |
| hook 完全不触发 | 针对 `mcp____` 写的 matcher 对插件捆绑的 MCP 服务器不匹配,那些是 `mcp__plugin____` |
| hook 脚本没拦住任何东西 | macOS / Linux 上忘了 `chmod +x`。没有执行权限的 hook 是**失败**而不是拦截 |
| skill 的 `/name` 能用但 Claude 从不自动调用 | frontmatter YAML 坏了。Claude Code 加载正文但元数据为空,所以没有描述可匹配。用 `--debug` 看解析错误 |
| skill 在自动补全里不出现 | 它在启动目录**下面**的嵌套 `.claude/skills/`,Claude 碰过那个目录的文件之后才可用 |
| 改了 subagent 定义没生效 | 同一目录树下两个文件声明了同一个 `name`,加载哪个取决于文件系统读取顺序。`/doctor` 会报告 |
| subagent 缺了它该有的工具 | 后台运行的 subagent 内置工具集被二次收窄,移除时不报错。设 `background: false` 保留完整工具集。见[subagent](/agents/subagents/) |
| `${CLAUDE_SKILL_DIR}` 的 allow 规则永远匹配不上 | 需要 v2.1.129 或更高版本,旧版本上保持字面字符串 |
| `--json-schema` 给了无效 schema 却拿到非结构化输出 | v2.1.205 之前不报错。升级后会直接报错退出 |
| `.claude/rules/` 里某条规则从不加载 | `paths` 里有无法解析的 glob(比如未转义的 `[`)。见[CLAUDE.md 上下文工程](/agents/claude-md-context/) |
| Read 工具对一批文件莫名报错 | v2.1.207 之前,rules 里一个无效 glob 会让该规则求值过的每个文件都失败 |
| 权限比预期宽 | 可能有两份 `settings.local.json`(v2.1.211 位置变过),两份里的权限规则都生效 |
| `claude daemon status` 变成了一句提示词 | `claude` 被别名成带 `--dangerously-skip-permissions`,且版本低于 v2.1.199 |
| 文档提到的标志报「未知参数」 | 先确认版本。`claude --help` 不列出全部标志,所以不在 `--help` 里不代表不可用 |
## 排查的通用顺序
[Section titled “排查的通用顺序”](#排查的通用顺序)
不确定问题在哪一层时:
1. `claude doctor` —— 安装健康度和设置文件解析错误,不启动会话
2. `/status` —— 看 `Setting sources` 确认哪些层真的加载了
3. `/context` —— 看上下文里什么在占空间,确认 CLAUDE.md 和 skill 列表加载了
4. `/doctor` —— 会话内检查,能给出精简建议
5. `claude --debug` 或 `--debug-file ` —— 带类别过滤,如 `--debug "api,hooks"`
6. `claude --safe-mode` —— 关掉所有自定义,确认问题是否来自某个配置
第 6 步是二分法的起点:`--safe-mode` 下问题消失,说明是配置引起的,逐层打开找到那一项。
# 全站待核实清单
> 各章「还没确认的点」的汇总,按不确定的性质分类,说明哪些是文档未覆盖、哪些是需要实测、哪些是版本差异。
这一页为什么存在
每章末尾都有「还没确认的点」,但散着放,读者要翻完全站才知道哪些结论是软的。这一页把它们集中起来。最后更新:2026-07-30。
## 三类不确定,可信度不同
[Section titled “三类不确定,可信度不同”](#三类不确定可信度不同)
分类比条目本身更重要,因为它决定了你该怎么对待这个不确定:
| 类型 | 含义 | 你该怎么办 |
| --------- | ---------------------- | --------------- |
| **文档未覆盖** | 官方文档没写,本站不猜 | 需要的话自己实测,或者等官方补 |
| **需要实测** | 文档有描述但不够具体,或者行为可能因环境而异 | 在你自己的环境里验证一次 |
| **版本差异** | 已知在某些版本上不同,但没跟踪完整 | 先确认自己的版本 |
## 文档未覆盖
[Section titled “文档未覆盖”](#文档未覆盖)
这些是官方文档确实没有说明的地方。本站选择留白而不是推测。
**配置与设置**
* `~/.claude.json` 的完整字段结构。只核实到官方 settings 文档 Global config settings 一节列出的几个 key。
* 配置文件自动备份的命名和位置。文档提到会保留最近五份带时间戳的备份,细节未见说明。
* `--max-budget-usd` 的计价基准。是否和 `/usage` 一样按标准列表价本地计算,未见说明。
**hooks**
* HTTP 类型 hook 的完整配置字段。文档主要在讲它的限制机制(`allowedHttpHookUrls`、`httpHookAllowedEnvVars`),配置项本身没有逐条列出。
* 各事件 stdin 输入 JSON 的完整字段。公共字段确认了,各事件特有字段没有逐个核实。
* 同一 matcher 组内多个 hook 并行返回冲突的 `permissionDecision` 时,最终采用哪个。
* `ConfigChange` 事件的输入字段,以及一次批量配置改动触发几次。
**权限模式**
* auto mode 的账户与订阅层级要求。文档提到有要求,具体哪些层级可用没有核实清楚。
* 分类器与 sandbox 的具体交互。两者都在限制命令行为,谁先生效、判断如何叠加。
* `soft_deny` 触发时的确认界面与 default 模式的权限提示是否相同。
**MCP**
* `WaitForMcpServers` 工具的具体行为。
* channels 机制(服务器主动推消息进会话)的完整配置流程。
* 自动后台化的边界情况。主对话里超过两分钟的 MCP 调用转为后台任务,但 subagent 调用、IDE 服务器调用不会——这些例外的完整规则。
* managed MCP 配置(`managed-mcp.json`、`allowedMcpServers`)的部署细节。
**CLAUDE.md 与 skills**
* `claudeMdExcludes` 对嵌套 rules 目录的匹配细节。「排除一个目录」和「排除目录下的具体文件」行为上是否有区别。
* 压缩后 CLAUDE.md 重新注入的具体时机:紧接压缩之后,还是下一轮请求时。
* skill 的 `hooks` frontmatter 字段完整配置格式(文档指向另一页,本站未核实)。
* skill 的 `paths` frontmatter 与 `.claude/rules/` 的 `paths` 触发时机是否完全一致。
* 压缩后重新附加 skill 时「前 5000 token」的截断边界:按 token 硬切还是按段落边界。
* `effort` 各档位在不同模型上的可用性对应表。
**subagent**
* `initialPrompt` 字段与用户提供的提示词如何拼接。
* `isolation: worktree` 的清理条件中「有改动」的判定标准。
* 兄弟名册(sibling roster)内容的更新时机,只知道是启动时快照。
* 并发限额下「恢复已完成 subagent 不检查限额」是否有其他兜底。
**headless**
* `--output-format json` 返回结构的完整字段(在 Agent SDK 文档里)。
* `stream-json` 各事件类型的完整定义。
* `claude -p` 本身在各类失败下的退出码。只确认了 `claude auth status` 和 `claude ultrareview` 两个子命令。
**成本**
* OpenTelemetry 导出的具体指标名与配置(在 monitoring 页)。
* `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` 的确切作用范围。官方 costs 页没有列出这个变量。
* prompt caching 里「什么样的上下文变化会导致缓存失效」。
## 需要实测
[Section titled “需要实测”](#需要实测)
文档有描述,但结论的可靠性取决于你的环境。
* **分类器判断的确定性**。同一条命令在同一配置下是否总得到相同结论,文档未说明。这直接影响「能不能靠 auto mode 做确定性防护」这个判断——本站的立场是不能,硬边界要用 `permissions.deny`。
* **自动记忆的写入判定**。Claude 根据「这条信息未来是否有用」决定要不要存,这个过程不可观测。
* **`/doctor` 精简建议的判定规则**。文档描述了它保留什么删什么,没有可预测的规则。
* **「agent team 约为标准会话 7 倍」的测量条件**。文档说的是 teammate 在 plan 模式下运行时,其他模式的倍数未给出。
* **skill 描述被截断后的实际影响**。预算按上下文窗口 1% 缩放,但「截断多少会导致匹配失败」需要在自己的 skill 集合上测。
## 版本差异(已知但未跟踪完整)
[Section titled “版本差异(已知但未跟踪完整)”](#版本差异已知但未跟踪完整)
这些行为在不同版本上不同,本站记录了已知的变化点,但不保证完整。**遇到与本站描述不符时,先确认版本。**
| 主题 | 已知变化 |
| ----------------------------------- | -------------------------------------------------------------------------- |
| settings key 归属 | v2.1.119 之前一批 `/config` 偏好项存在 `~/.claude.json` 而非 `settings.json`。更早的版本未跟踪 |
| `settings.local.json` 位置 | v2.1.211 起在 git 仓库根读写,之前在启动目录 |
| Windows managed 路径 | v2.1.75 起不再读 `C:\ProgramData\ClaudeCode\` |
| `defaultMode: auto` 来源限制 | v2.1.142 起忽略项目层和 local 层 |
| `autoMode` 来源限制 | v2.1.207 之前 `.claude/settings.local.json` 也在读取范围内 |
| MCP 缺 `type` 报错措辞 | v2.1.202 变过 |
| skill 重复调用 | v2.1.202 之前每次重新调用都追加一份完整副本 |
| `${CLAUDE_SKILL_DIR}` 在 allow 规则里替换 | 需要 v2.1.129+ |
| skill 布尔字段取值 | v2.1.218 之前只认 `true` / `false` |
| Explore 的模型 | v2.1.198 之前固定 Haiku,之后继承主对话 |
| subagent 嵌套深度默认值 | v2.1.172–216 是 5 且不可改;v2.1.217–218 是 1;v2.1.219 起是 3 |
| subagent `tools` 全部解析失败 | v2.1.208 之前会带零工具启动而不是拒绝 |
| subagent frontmatter hook 信任要求 | v2.1.218 之前可从未信任文件夹运行 |
| subagent 输出扫描 | 需要 v2.1.210+ |
| rules 无效 glob 的影响 | v2.1.207 之前会让该规则求值过的每个文件读取都失败 |
| `/usage` 总数是否跨 `/clear` 累积 | v2.1.211 起归零,之前累积到进程结束 |
| `--json-schema` 无效 schema | v2.1.205 之前静默产出非结构化输出 |
| `--max-turns` 与排队消息 | v2.1.205 之前那条消息被丢弃 |
| `--permission-prompt-tool` 慢启动服务器 | v2.1.206 之前会以「MCP 工具未找到」退出 |
| `daemon` 子命令路由 | v2.1.199 之前带前置 `--dangerously-skip-permissions` 时不执行 |
| `/doctor` 精简检查 | 需要 v2.1.206+ |
| `manual` 作为 `default` 别名 | 需要 v2.1.200+ |
| `classifyAllShell` | 需要 v2.1.193+ |
| `claude mcp login` / `logout` | 需要 v2.1.186+ |
| 保留 MCP 服务器名与 `name` 里的 `:` | v2.1.218 之前被接受 |
| `--append-subagent-system-prompt` | 需要 v2.1.205+ |
| `--forward-subagent-text` | 需要 v2.1.211+ |
| `auto-mode defaults` / `reset` 命令 | 分别需要 v2.1.208+ / v2.1.212+ |
| `--max-budget-usd` 上限强制 | 需要 v2.1.217+ |
| fork 命令名 | v2.1.212 起是 `/subtask`;v2.1.161–211 是 `/fork`。现在的 `/fork` 语义不同 |
## 本站的核实方式
[Section titled “本站的核实方式”](#本站的核实方式)
写清楚这一点,是为了让你能判断本站结论的可靠程度:
* **主要来源是官方文档**。每章末尾的「参考」列出了具体页面。
* **不推测**。文档没写的就写进「还没确认的点」,不用「应该是」「大概」填空。
* **版本敏感的行为标注版本号**。因为 Claude Code 的迭代速度让「当前行为」这个说法很快过期。
* **过期内容不删**。发现某个结论错了或者失效了,在原位加更新说明,保留原文——因为搜到旧报错的人需要知道「这个说法曾经是对的,现在变了」。
发现本站的错误,[提 issue](https://github.com/tobenot/ai-atlas/issues) 或直接在页面底部点编辑。带上你的版本号会让修正快很多。