首页 › tutorials › 让自定义 Skill 被精准触发

概述

本教程带你从零创建一个会被 Claude 精准触发的自定义 Skill。重点不在"写一个能跑的 markdown",而在触发工程(Trigger Engineering)——为什么你的 Skill 写了却不被调用、description 预算是怎么分配的、如何用 paths/when_to_use//doctor 让它在正确时刻加载。适合已跑通 Claude Code、想把重复指令固化成可复用技能的开发者。

前置条件


第 0 步:先搞清楚 Skill 到底是什么

很多人把 Skill、CLAUDE.md、commands、MCP 混为一谈。这一步不搞清楚,后面写出来的 Skill 注定"不触发"。

机制 何时进入上下文 适合放什么
CLAUDE.md 每次会话都加载 事实型约束(项目约定、技术栈)
Skill 主体 只在被触发时加载,描述常驻 流程型步骤、可复用工作流
commands(旧) 与 Skill 等价,已被合并 已存在的 .claude/commands/*.md 仍可用
MCP server 进程常驻,提供工具 需要稳定 API/外部数据源

关键区别就一句:CLAUDE.md 的内容每轮都烧 token,Skill 的正文只有在被调用那一刻才进上下文。所以"一段越长越长的流程",应该从 CLAUDE.md 搬进 Skill,而不是继续往 CLAUDE.md 里塞。判断标准也很简单:当 CLAUDE.md 的某段已经从"事实"长成了"操作步骤",就是它该变成 Skill 的时候。

Skill 遵循开放的 Agent Skills 标准,可跨多个 AI 工具复用;Claude Code 在此之上扩展了调用控制、子代理执行、动态上下文注入等能力。

第 1 步:选定主题,先想清楚"谁会在什么时候喊它"

跳过这一步直接动手,是 Skill 不触发的头号原因。先用三句话钉死你的 Skill:

  1. 它解决的一个具体动作——不是"帮助写代码",而是"为当前版本生成 release notes"。
  2. 用户会怎么自然地喊它——把平时你在聊天框里手敲的那句话原样记下来。
  3. 它需要什么输入、产出什么——from-tag、to-tag → 分类好的更新日志。

本教程的实战案例就定为 release-notes:读取两个 git tag 之间的提交历史,按 Conventional Commits 规范归类成"新功能 / 修复 / 破坏性变更 / 其他",产出一份发布说明。它和"提交信息生成"不同——后者针对单次 commit,前者针对整段发版区间。

第 2 步:搭出标准目录骨架

个人级 Skill 放在 ~/.claude/skills/ 下,跨你所有项目可用;项目级放在仓库的 .claude/skills/,只随这个仓库生效。先建目录:

mkdir -p ~/.claude/skills/release-notes/scripts
mkdir -p ~/.claude/skills/release-notes/examples

标准结构长这样,只有 SKILL.md 是必须的,其余按需:

release-notes/
├── SKILL.md            # 入口,必填:frontmatter + 指令
├── template.md         # 输出模板,Claude 按它填
├── reference.md        # Conventional Commits 分类规则,按需加载
├── examples/
│   └── sample.md       # 一份期望输出样例
└── scripts/
    └── categorize.sh   # 可执行脚本,被运行而非被读

目录名 release-notes 会自动成为命令 /release-notes,和 frontmatter 里的 name 字段无关(name 只影响列表里的显示名)。这是新手最常踩的坑之一:改了 name 却纳闷为什么 /xxx 还是旧名字。

第 3 步:写 description——触发工程的核心

整个 Skill 最值钱的一行就是 description。Claude 在每轮对话里,拿着用户的话去和所有 Skill 的描述做匹配,决定要不要把某个 Skill 的正文加载进来。描述写得泛或写得偏,触发就走样。

先看一份反例:

---
description: 帮助生成文档
---

问题在于"文档"太宽,Claude 在你让它写 README、写注释、写 API 文档时都可能误触发,也可能都不触发。再看正例:

---
name: release-notes
description: 为当前或指定版本生成发布说明(release notes / changelog)。当用户询问"这次发版改了什么""上一次 tag 到现在有哪些变更""帮我写 v2.3.0 的更新日志"时使用。
when_to_use: |
  触发短语举例:
  - "帮我写这次发布的 release notes"
  - "上个版本到现在改了什么"
  - "为 v2.3.0 生成更新日志"
  - "整理这周的 commit 做发布"
argument-hint: "[from-tag] [to-tag]"
arguments: [from, to]
allowed-tools: Bash(git log *) Bash(git tag *)
paths: "src/**, package.json, CHANGELOG.md"
---

这里每一行都在为触发服务,逐个拆解:

description(推荐):第一句话就把"做什么 + 什么时候用"说死,关键词用用户原话(发布说明、changelog、更新日志)。官方规则是 description + when_to_use 合起来在技能列表里最多 1536 字符就被截断,所以要把最关键的用例放在最前面。

when_to_use(可选):专门塞触发短语和示例请求,会被拼到 description 后面。这是让 Claude"听懂人话"的利器——与其描述功能,不如直接列出"用户会怎么说"。注意它也算在那 1536 字符的预算里。

argument-hint / arguments:前者是 / 菜单自动补全里显示的提示(如 [from-tag] [to-tag]),后者把位置参数映射成具名变量,这样正文里就能用 $from、$to 而不是 $0、$1。

allowed-tools:让 Claude 在本 Skill 激活期间,免确认地运行列出的工具。注意它只是免确认,不限制可用范围——你的权限设置仍对未列出工具生效。Bash(git log *) 这种带通配的写法,把授权收敛到具体子命令,避免给整个 Bash 开绿灯。

paths(可选,很关键):Glob 模式,只有当你正在处理匹配文件时,Claude 才会自动加载这个 Skill。对 release-notes 这种其实不太依赖路径的 Skill,这个字段意义不大;但对"只在前端代码里生效的命名规范 Skill",paths: "apps/web/**" 能极大减少误触发。本例加上是为了演示——它会让 Skill 在你编辑 src/ 下文件或 CHANGELOG.md 时更易被自动选中。

这一行还顺带演示了一个常见取舍:allowed-tools 写在项目级 .claude/skills/ 时,要等你通过工作区信任对话框后才生效,和 .claude/settings.json 里的权限规则一致。提交进仓库的 Skill 等于把自己的工具授权写进团队代码,信任仓库前务必先看一遍。

第 4 步:写正文,把动态数据"提前注入"

正文是 Claude 触发后真正读到的指令。这里的杀手锏是动态上下文注入:用 !`命令` 语法,让 Claude Code 在把正文交给 Claude 之前先执行命令,再用输出替换占位符。Claude 收到的是真实数据,不是命令本身。

## 当前发版范围
从 `$from` 到 `$to`(若未给参数,默认取最近两个 tag)。

## 提交历史(已自动拉取)
!`git log $from..$to --pretty=format:"- %s" --no-merges 2>/dev/null || git log --pretty=format:"- %s" --no-merges -20`

## 你的任务
1. 读取 `reference.md` 里的 Conventional Commits 分类规则(仅在需要分类细节时读)。
2. 按规则把上面的提交归类到:✨ 新功能 / 🐛 修复 / ⚠️ 破坏性变更 / ♻️ 重构 / 📦 其他。
3. 严格照 `template.md` 的结构产出发布说明。
4. 若检测到 `BREAKING CHANGE` 或 `!:` 提交,在顶部用醒目方式列出迁移提示。

几条注入语法要点,踩过的人才懂:

第 5 步:加支撑文件,落实渐进披露

把所有规则塞进 SKILL.md 是第二个常见错误——一旦 Skill 被触发,正文会整段常驻上下文直到会话结束,每行都是反复燃烧的 token。官方建议 SKILL.md 控制在 500 行以内,长内容外移到支撑文件,只在用到时才被读。

reference.md(只在需要分类细节时加载):

# Conventional Commits 分类速查
- `feat:` / `feat(scope):` → 新功能 ✨
- `fix:` → 修复 🐛
- 后缀 `!` 或 footer `BREAKING CHANGE:` → 破坏性变更 ⚠️(必须置顶并附迁移说明)
- `refactor:` → 重构 ♻️(不改变外部行为)
- `docs:`,`chore:`,`test:`,`ci:`,`perf:` → 归到"其他 / 内务 📦"
- 中文提交信息(无前缀):按语义归类,拿不准归"其他"。

template.md(输出骨架):

# v<版本号> 发布说明
> 发布日期:<YYYY-MM-DD>

## ⚠️ 破坏性变更(如有)
-

## ✨ 新功能
-

## 🐛 修复
-

## 📦 其他
-

关键是在 SKILL.md 里显式引用这些文件,否则 Claude 不知道它们存在、何时该读:

## 资源
- 完整分类规则见 [reference.md](reference.md)
- 输出结构照 [template.md](template.md)
- 期望格式见 [examples/sample.md](examples/sample.md)

第 6 步:加可执行脚本,把重活交给代码

当分类逻辑复杂到写在 prompt 里又啰嗦又不可靠时,就外移成脚本。Skill 能捆绑任意语言的脚本。用 ${CLAUDE_SKILL_DIR} 引用脚本路径,它指向当前 SKILL.md 所在目录,无论 Skill 装在个人级、项目级还是插件级都能正确解析。

scripts/categorize.sh:

#!/usr/bin/env bash
# 读取 git log,按 Conventional Commits 归类计数
set -euo pipefail
FROM="${1:-}"; TO="${2:-HEAD}"
range="${FROM:+$FROM..}$TO"

git log $range --pretty=format:"%s" --no-merges 2>/dev/null \
| awk '
  /^feat(\(.+\))?!?:/   {feat++; next}
  /^fix(\(.+\))?!?:/     {fix++; next}
  /^refactor:/           {ref++; next}
  /!/                    {brk++; next}
                          {other++}
  END {
    printf "✨ 新功能:%d\n🐛 修复:%d\n♻️ 重构:%d\n⚠️ 破坏性:%d\n📦 其他:%d\n", \
           feat, fix, ref, brk, other
  }'

在 SKILL.md 里这样调用(把它作为给 Claude 的"可选项",而非强制):

## 可选:先跑分类统计
在动笔前,可先运行统计,心里有数:
```bash
bash "${CLAUDE_SKILL_DIR}/scripts/categorize.sh" "$from" "$to"
> 给脚本加 `chmod +x` 并不强制(这里用 `bash xxx.sh` 调用),但养成习惯更好。脚本设计原则:**把确定性的脏活交给代码,把判断性的表达交给 Claude**——awk 负责数数,Claude 负责遣词造句和写迁移说明。

## 第 7 步:用 frontmatter 开关,精确控制"谁、在哪、怎么跑"

到这一步你的 Skill 已经能用了。剩下的 frontmatter 字段,是用来回答三个问题的:

**问题一:该你手动触发,还是让 Claude 自动触发?**

| 字段设置 | 你能 `/调用` | Claude 能自动触发 | 描述是否常驻上下文 |
| :--- | :--- | :--- | :--- |
| 默认 | 是 | 是 | 是 |
| `disable-model-invocation: true` | 是 | **否** | **否**(更省 token) |
| `user-invocable: false` | **否** | 是 | 是 |

`release-notes` 这类有明确"发布"动作、最好由人拍板时机的流程,适合加 `disable-model-invocation: true`,避免 Claude 看你 push 了就自作主张写发布说明。反过来,像"遗留系统背景知识"这种用户不会主动 `/`、却希望 Claude 在相关时自己想起来的,就该 `user-invocable: false`。

**问题二:要不要跑在隔离子代理里?**

加 `context: fork`(可配 `agent: Explore`)让 Skill 在独立上下文跑,`SKILL.md` 正文成为驱动子代理的 prompt。好处是不污染主对话、上下文干净;代价是**它看不到你的对话历史**。所以 `context: fork` 只适合"有明确任务步骤"的 Skill——纯指南型 Skill(只有约定没有任务)交给子代理,它会因为拿不到可执行动作而无功而返。

**问题三:要不要临时切模型/切算力?**

`model:` 可在 Skill 激活期间临时换模型(下一轮 prompt 自动恢复);`effort:` 可临时调档(`low/medium/high/xhigh/max`)。比如让 `release-notes` 用 `effort: low` 快速出一版草稿,省时省 token。

此外还有 `disallowed-tools`(激活期间**移除**某些工具,如让无人值守 Skill 不能调 `AskUserQuestion`)、`hooks`(绑定本 Skill 生命周期的钩子)、`shell: powershell`(Windows 下内联命令走 PowerShell)。这些字段官方文档列出有十余个,本教程挑了对触发与行为影响最大的几个讲透。

## 第 8 步:验证——别把"它动了"当成"它对了"

这是最被忽略、却最该做的一步。官方原话很到位:**"看到 Skill 被触发,只说明 Claude 找到了它,不说明它做到了你想要的。"**

按顺序做三件事:

**1. 确认它被发现且能被触发。**

What skills are available?

Claude 会列出当前可用 Skill。确认 `release-notes` 在列、描述没被截断。然后故意用**自然语言**(不是 `/release-notes`)测自动触发:

上个版本到现在都改了啥,整理成发布说明

如果 Claude 自动用了这个 Skill,说明 `description`/`when_to_use` 的关键词命中了。

**2. 用 `/doctor` 查描述预算。**

Skill 越多,描述越可能被砍。预算是**模型上下文窗口的 1%**,超了就**从你最不常调用的 Skill 开始砍描述**。`/doctor` 会直接告诉你哪些 Skill 的描述被缩短或丢弃。需要的话:

- 调大预算:`settings.json` 里设 `"skillListingBudgetFraction": 0.02`(2%),或环境变量 `SLASH_COMMAND_TOOL_CHAR_BUDGET` 设固定字符数;
- 给低优先 Skill 瘦身:`skillOverrides` 里把不常用的设成 `"name-only"`,只列名不占描述预算。

**3. 做"开/关"基线对照(判断质量,而非触发)。**

收集几条真实请求,**在全新会话**里分别跑两次:一次 Skill 可用、一次用 `skillOverrides` 设成 `"off"` 关掉,对比产出。为什么强调全新会话?因为你刚写完 Skill 时,作者上下文会掩盖指令的漏洞——同一个会话里 Claude "记得"你的意图,换一个干净会话才暴露真实差距。想做系统化对照,装官方 `skill-creator` 插件:

/plugin install skill-creator@claude-plugins-official /reload-plugins

让它跑隔离的测试用例、打分、出 benchmark,甚至做两个版本的盲测 A/B,确认你的改动是真改进才提交。

> 改了 `SKILL.md` 不用重启——Claude Code 会**实时监听** Skill 目录变化,保存即生效(仅限文本;若 Skill 同时是插件,改了 `hooks/`、`.mcp.json` 等要 `/reload-plugins`)。

## 第 9 步:分发——从个人到团队到全组织

按受众选分发方式:

- **项目级**:把 `.claude/skills/release-notes/` 提交进版本控制,团队 clone 即得;
- **插件**:在仓库根加 `.claude-plugin/plugin.json`,就能以插件形式打包,顺带带上 agents、hooks、MCP server;
- **托管级**:通过 managed settings 全组织下发,用户无法覆盖。

插件 Skill 用 `plugin-name:skill-name` 命名空间,天然不会和其他层冲突。同名优先级是 **enterprise > personal > project**,任意一层都会盖掉同名内置 Skill(你在项目 `.claude/skills/` 放个 `code-review`,就会替掉内置的 `/code-review`)。

---

## 实战案例:完整可运行的 `release-notes` Skill

把前面零散的片段拼成最终产物。四个文件,复制即可跑(前提是仓库有 git 历史和 Conventional Commits 风格的提交)。

**`~/.claude/skills/release-notes/SKILL.md`**:

```yaml
---
name: release-notes
description: 为当前或指定版本生成发布说明(release notes / changelog)。当用户询问"这次发版改了什么""上一次 tag 到现在有哪些变更""帮我写 v2.3.0 的更新日志"时使用。
when_to_use: |
  触发短语:
  - "帮我写这次发布的 release notes"
  - "上个版本到现在改了什么"
  - "为 v2.3.0 生成更新日志"
  - "整理这周的 commit 做发布"
argument-hint: "[from-tag] [to-tag]"
arguments: [from, to]
allowed-tools: Bash(git log *) Bash(git tag *) Bash(bash *)
disable-model-invocation: false
---

## 范围
从 `$from` 到 `$to`;若用户未给参数,默认取最近两个 tag 之间。

## 提交历史(已自动拉取)
!`git log $from..$to --pretty=format:"- %s" --no-merges 2>/dev/null || git log --pretty=format:"- %s" --no-merges -30`

## 可选:先看分类统计
```bash
bash "${CLAUDE_SKILL_DIR}/scripts/categorize.sh" "$from" "$to"

任务

  1. 需要分类细节时读 reference.md。
  2. 按规则把提交归入 ✨新功能 / 🐛修复 / ⚠️破坏性变更 / ♻️重构 / 📦其他。
  3. 严格照 template.md 结构产出;参考 examples/sample.md 的语气。
  4. 检测到 BREAKING CHANGE 或 !: 时,顶部用醒目方式列迁移提示。
**`template.md`**、**`reference.md`**、**`scripts/categorize.sh`** 见第 4–6 步,原样落入目录即可。

**预期效果**:在一个有提交历史的仓库里输入"把 v1.2.0 到现在的改动整理成发布说明",Claude 自动触发本 Skill,先 `git log` 拉真实提交,跑一遍分类统计,再照模板输出带 emoji 分组、破坏性变更置顶的发布说明。若没自动触发,直接 `/release-notes v1.2.0 HEAD` 手动调用。

---

## 工作流全景图

下面这张图把"从用户输入到 Skill 正文加载"的完整生命周期画清楚了,也是排查触发问题的思维地图:

```mermaid
flowchart TD
    A[用户输入一句话] --> B{Skill 描述预算内?}
    B -- 否:被砍/丢弃 --> X[/doctor 查看<br/>调 skillListingBudgetFraction/]
    B -- 是:描述常驻 --> C[Claude 用描述+when_to_use 匹配]
    C --> D{命中且未被禁?}
    D -- disable-model-invocation:true --> E[仅 /手动调用可达]
    D -- paths 限定 --> F{正在编辑匹配文件?}
    F -- 否 --> G[不自动加载]
    F -- 是 --> H[触发:加载 SKILL.md 正文]
    D -- 默认 --> H
    H --> I[执行 !动态注入:git log 等]
    I --> J{context: fork?}
    J -- 是 --> K[在隔离子代理跑<br/>看不到主对话历史]
    J -- 否 --> L[正文常驻主上下文<br/>直到会话结束/压缩]
    K --> M[按正文产出发布说明]
    L --> M
    M --> N[对照无 Skill 基线<br/>验证产出质量]

常见问题速查

症状 根因 解决
写完 Skill,Claude 不自动调用 description 关键词和用户原话对不上 把用户真实说法写进 when_to_use;关键词前置
描述总被截断 超 1536 字符或预算不够 精简描述;/doctor 查;调 skillListingBudgetFraction
/release-notes 还是旧名字 以为改 name 就改命令名 命令名来自目录名,改目录名才生效
!`git log` 没执行 ! 紧跟前导字符 ! 必须在行首或紧跟空白;多行用 ```!
误触发(不该用却用了) 描述太泛 收窄描述;加 paths 限定;或 disable-model-invocation: true
项目级 allowed-tools 不生效 没过工作区信任对话框 先信任仓库目录;信任前审一遍 Skill 授的工具
改了文件没生效 新建了会话启动时不存在的顶级目录 重启 Claude Code;改已有文件则自动热加载
触发了但产出不对 缺基线对照 新会话里开/关各跑一次,或用 skill-creator 跑 eval

延伸阅读

训练营适用性评估

参考来源

  1. Extend Claude with skills — Claude Code 官方文档 — Skill 创建、frontmatter 字段、触发与分发权威说明,本文第 2–9 步的主要依据。
  2. Claude Code Skill Frontmatter: Every YAML Option Explained — 逐字段解读十余个 frontmatter 选项及示例,第 7 步字段取舍的参考。
  3. How Claude Code Automatically Triggers Skills vs Manual Invocation — 自动触发与手动调用的机制对比,第 3、8 步触发工程的补充视角。
  4. Skill File Anatomy — Claude Code Skills — Skill 文件结构与渐进披露图解,第 2、5 步骨架与分层加载的佐证。
  5. Evaluating skill output quality — agentskills.io — 评测文件格式与迭代工作流,第 8 步基线对照与延伸阅读的来源。

[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。

51学通信知识星球海报-扫码加入