Skip to content

08.7 Skill 的文件读取规则与上下文占用

Skill 目录里放了 8 个文件,Claude 会读哪几个?答案比「全读」和「只读 SKILL.md」都更精确一点。

读完你能做什么:说清 Skill 目录里每类文件的读取时机,用五级强度的手段确保某个文件不被读取,并知道 Skill 正文进入上下文后的驻留规则。


一、只有一个文件是入口

SKILL.md 是唯一的入口文件,也是唯一被自动读取的文件。

一个 Skill 目录里的读取规则:

文件是否自动读取何时进入上下文
SKILL.md 的 YAML frontmatter会话启动时,进系统提示词(name + description
SKILL.md 的 markdown 正文Skill 被调用时,作为一条消息进入对话
同目录下的其他 .md只有当 Claude 决定读它时
scripts/ 下的脚本代码永不进上下文,只有执行输出进
模板、示例、数据文件只有当 Claude 决定读它时
README.md它不是入口,不会被当作 SKILL.md

所以「Skill 文件夹里的每个 md 文件都会被读取吗」的答案是:不会。默认只有 SKILL.md 会。

官方对这一点的表述是:Claude 只在文件被引用时才访问它们。一个 Skill 可以捆绑几十个参考文件,未被读取的部分成本为零。


二、Claude 决定读一个文件的三个触发源

既然不会全读,那什么情况下会读某个文件?只有三种:

触发源说明你的控制力
SKILL.md 里引用了它最主要的来源。正文里用相对链接指向 reference.md,Claude 就知道它存在、装了什么、什么时候该读完全可控
任务需要,Claude 自主探索Claude 有 Glob / Grep / Read 工具。当它在排查问题、或觉得目录里可能有线索时,可能主动列目录并读文件不完全可控
你显式让它读@ 提及、直接说文件名完全可控

第二条是唯一的不确定性来源。这也是为什么下一节要分五个强度等级。


三、确保一个文件不被读取:五级强度

需求场景很常见:想在 Skill 目录里放一份给人类维护者看的说明(设计意图、变更历史、待办),但不希望它消耗上下文。

级别手段强度代价
L0不在 SKILL.md 里引用它弱(去掉主要触发源)
L1用命名与位置表明用途弱(叠加语义信号)
L2SKILL.md 里显式写「不要读」中(软指令)几十 token 常驻
L3permissions.deny 里加 Read 规则强(工具层硬拦截)配置一行
L4PreToolUse hook 拦截强,且可审计写一个脚本
L5把文件放到 Skill 目录之外完全保证维护上分开放

实务上 L0 + L1 + L2 组合已经足够,涉及敏感内容再上 L3。

L0 + L1:约定层

text
my-skill/
├── SKILL.md            # 唯一入口,只在这里引用需要 Claude 读的文件
├── reference.md        # SKILL.md 里引用了 → Claude 需要时会读
├── scripts/
│   └── validate.py     # 执行,代码不进上下文
└── MAINTAINERS.md      # SKILL.md 里从不提及 → 默认不会被读

README.mdMAINTAINERS.md 这类名字有额外好处:它们在语义上就表示「给人看的」,而 SKILL.md 才是给 Claude 的入口。

L2:在正文里下一条明确指令

SKILL.md 末尾加一小节,成本约 40 token:

markdown
## 读取原则

- 需要详细规范时读 `reference.md`,只读相关小节。
- `MAINTAINERS.md``CHANGELOG.md` 面向人类维护者,执行任务时不要读取。

这比「不提它」更强:不提只是没给出理由,明确写出来相当于给了一条负向指令。

L3:权限规则硬拦截

~/.claude/settings.json 或项目 .claude/settings.json 里:

json
{
  "permissions": {
    "deny": [
      "Read(~/.claude/skills/my-skill/MAINTAINERS.md)",
      "Read(~/.claude/skills/*/CHANGELOG.md)"
    ]
  }
}

路径用 gitignore 语法,四种前缀含义不同,写错是最常见的失败原因:

写法含义例子解析为
//path文件系统绝对路径Read(//Users/alice/secrets/**)/Users/alice/secrets/**
~/path从家目录开始Read(~/.claude/skills/x/NOTES.md)
/path相对于配置文件所在位置,不是文件系统根写在项目 settings.json 里 → <项目根>/path
path./path相对于当前目录Read(*.env)<cwd>/*.env

三个必须知道的边界:

  1. 只有 Read(path)Edit(path) 规则会被文件权限检查采用。 写成 Write(docs/**)Glob(docs/**) 会被接受但永远不被查询,并在启动时告警。用 Edit(...) 代替 Write/NotebookEdit/MultiEdit,用 Read(...) 代替 Glob。需 v2.1.210+。
  2. Read 的 deny 规则同时会挡住 Edit(含在该路径新建文件)。WriteNotebookEdit 不在覆盖范围内,需要彻底禁改就再加一条 Edit 规则。需 v2.1.208+。
  3. 规则覆盖内置文件工具,以及 Claude Code 能识别的 bash 文件命令catheadtailsed)。不覆盖任意子进程 —— 一个自己 open() 文件的 Python 脚本绕得过去。需要操作系统级别的强制,要启用沙箱。

L4:Hook 拦截并留痕

需要审计(谁试图读、什么时候)时用这一级:

json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Read",
        "hooks": [
          { "type": "command", "command": "~/.claude/hooks/block-maintainer-docs.sh" }
        ]
      }
    ]
  }
}
bash
#!/bin/bash
# ~/.claude/hooks/block-maintainer-docs.sh
input=$(cat)
path=$(echo "$input" | jq -r '.tool_input.file_path // ""')

case "$path" in
  */MAINTAINERS.md|*/CHANGELOG.md)
    echo "$(date -Is) blocked: $path" >> ~/.claude/logs/blocked-reads.log
    echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"该文件面向人类维护者,不应进入上下文"}}'
    ;;
  *)
    echo '{}'
    ;;
esac
bash
chmod +x ~/.claude/hooks/block-maintainer-docs.sh

Hook 本身不占任何上下文,只有它的输出会进上下文。

L5:放到目录之外

唯一的完全保证。把维护文档放进仓库的 docs/skills/my-skill.md,Skill 目录里只留执行需要的东西。

代价是两处内容要同步维护,适合内容较多或确实敏感的场景。


四、目录扫描的边界

除了 SKILL.md,Claude Code 还会在 Skill 目录里主动看什么?

行为范围
启动时的 Skill 发现只解析 SKILL.md 的 frontmatter
实时变更检测只覆盖 SKILL.md 的文本内容。其他文件的改动不会被自动感知
目录是插件时若目录下有 .claude-plugin/plugin.json,它会作为插件加载,hooks/.mcp.jsonagents/output-styles/ 也会被识别(改动需 /reload-plugins 生效)
符号链接会跟随符号链接读取目标目录的 SKILL.md;同一目标从多个位置可达时只加载一次

所以:只要目录里没有 .claude-plugin/plugin.json,除 SKILL.md 外的文件在加载阶段完全不会被碰。


五、进入上下文之后:不会被释放

这是与「读取规则」同等重要的另一半。

Skill 被调用时,渲染后的 SKILL.md 内容作为一条消息进入对话,并在本次会话的剩余时间里一直留在那里。Claude Code 不会在后续轮次重新读取该文件。

几条直接影响写法的规则:

规则写法上的含义
正文常驻整个会话每一行都是重复成本。说「做什么」,别说「为什么」
Claude 不会重读文件写成常驻约束(「本任务中始终…」),而不是一次性步骤(「现在请…」)
重复调用会去重渲染结果完全相同时只追加一句提示,不重复正文(需 v2.1.202+)。参数或动态注入变了则会追加完整副本
压缩后按配额重新附着每个 Skill 保留前 5,000 token,所有 Skill 共享 25,000 token 总预算,从最近调用的开始填,超出的老 Skill 被整个丢弃

最后一条决定了排版:最重要的指令必须写在 SKILL.md 的开头,因为截断保留的是文件前部。

想让 Skill 用完即走,只有两条路:

yaml
# 路线一:在子代理里执行,内容从不进主上下文
---
name: deep-research
description: 深入调研一个主题并返回结论
context: fork
agent: Explore
---
yaml
# 路线二:连 description 都不常驻,只有你手动 /deploy 时才加载
---
name: deploy
description: 把应用部署到生产环境
disable-model-invocation: true
---

三种可见性组合的常驻成本:

frontmatter你能调用Claude 能自动调用常驻成本
(默认)description 常驻
disable-model-invocation: true
user-invocable: falsedescription 常驻

六、一份推荐的目录布局

text
my-skill/
├── SKILL.md              # 入口。前 5,000 token 放最重要的约束
├── reference.md          # SKILL.md 引用,需要时才读
├── examples.md           # SKILL.md 引用,需要时才读
├── templates/
│   └── endpoint.md       # 模板,用到才读
├── scripts/
│   └── validate.py       # 执行,代码不进上下文
└── MAINTAINERS.md        # 不引用 + 在 SKILL.md 里声明不读 + 可选 deny 规则

配套的 SKILL.md 收尾:

markdown
## 读取原则

- 需要完整字段规范时读 `reference.md`,只读用得上的小节。
- 需要写法参考时读 `examples.md`
- 校验一律跑 `${CLAUDE_SKILL_DIR}/scripts/validate.py`,不要手写等价逻辑。
- `MAINTAINERS.md` 面向人类维护者,执行任务时不要读取。

延伸阅读

基于 VitePress 构建 · 内容采用原作者授权