本教程聚焦 Claude Code Skill 中两个最实用却常被忽略的机制:参数化($ARGUMENTS/$N/命名参数/argument-hint)与动态上下文注入(!command`` 预处理)。学完你将能构建带变量、能"开箱即装现网数据"、可携带脚本与审批白名单的可复用工作流 Skill,适合已会用基础 Skill、希望把重复流程沉淀为命令行工具的中级开发者。
allowed-tools、disable-model-invocation、context: fork 等字段,新版行为更稳定)git、gh、jq 等命令行工具)SKILL.md,理解 description 的作用本教程不重复"渐进披露三层结构""子 Agent 编排""触发词调优"等内容,而是从另一个切面切入:让一个 Skill 变成可以接收参数、可以预装实时数据、可以携带可执行脚本的"小型命令行程序"。
很多人把 Skill 当成"一段提示词",这低估了它。Skill 的执行其实是两段式流水线:
flowchart LR
A["/release-preflight v1.4.0"] --> B["1. 参数替换<br/>$ARGUMENTS / $N / 命名参数"]
B --> C["2. 上下文注入<br/>!`command` 在 Claude 之前执行"]
C --> D["3. 渲染后的 SKILL.md<br/>作为单条消息注入对话"]
D --> E["Claude 按指令<br/>调用工具完成任务"]
E --> F["结果返回主对话<br/>SKILL.md 内容常驻本会话"]
关键在于第 2 步:!command`` 不是让 Claude 去执行命令,而是在 Claude 看到任何东西之前,由 Claude Code 的运行时先把命令跑完、把输出文本替换到那个位置。所以 Claude 收到的永远是"现网的快照数据",而不是"一个待执行的命令"。这是消除幻觉、让 Skill 输出可复现的核心手段。
理解这一点后,后面所有字段都只是给这条流水线加"旋钮"。
在个人技能目录下创建一个发布前检查的 Skill,它接收一个版本号作为参数:
mkdir -p ~/.claude/skills/release-preflight/scripts
写入 ~/.claude/skills/release-preflight/SKILL.md:
---
name: release-preflight
description: 发布前自检。当用户要打 tag、发版、上线前做合规与质量检查时使用。
argument-hint: [version]
arguments: [version]
disable-model-invocation: true
---
## 发布目标
即将发布版本:**$version**
## 当前仓库快照
- 当前分支:!`git rev-parse --abbrev-ref HEAD`
- 最新提交:!`git log -1 --pretty=format:'%h %s'`
- 未提交文件:!`git status --porcelain | head -20`
## 自检清单
1. 若上述"未提交文件"非空,先提示用户提交或 stash,不要自动 commit。
2. 跑测试脚本:${CLAUDE_SKILL_DIR}/scripts/check.sh
3. 检查 CHANGELOG 是否包含 $version。
4. 给出"可以打 tag $version"或"阻塞项"结论。
在仓库里执行 /release-preflight v1.4.0,Claude 收到的实际内容(伪表示)是:
## 发布目标
即将发布版本:**v1.4.0**
## 当前仓库快照
- 当前分支:main
- 最新提交:a1b2c3d feat: add export api
- 未提交文件: M src/api.py
## 自检清单
1. 若上述"未提交文件"非空,先提示...
2. 跑测试脚本:/home/you/.claude/skills/release-preflight/scripts/check.sh
...
注意三个细节:$version 被替换成了 v1.4.0;!git ...` 被替换成了**真实输出**;${CLAUDE_SKILL_DIR}` 被替换成了 Skill 目录的绝对路径——这意味着脚本路径不依赖你当前的工作目录,在任意子目录调用都正确。
| 现象 | 原因 | 解决 |
|---|---|---|
$ARGUMENTS 原样出现,没被替换 |
你用了未声明的占位符,或参数没传 | 用 arguments: [version] 声明后再用 $version;或显式写 $ARGUMENTS |
!git status没执行,显示成字面量 | `!` 没出现在**行首或空格之后** | `KEY=!`cmd 这种紧跟字符的形式不会触发,需独占一行或前面是空白 |
||
| 脚本路径找不到 | 用了相对路径 ./scripts/check.sh |
必须用 ${CLAUDE_SKILL_DIR}/scripts/check.sh |
| Claude 不肯跑脚本 | 缺少 allowed-tools 授权 |
见第三步 |
allowed-tools 预授权工具,免去逐次确认默认情况下,Claude 每次执行 bash 命令都会弹权限确认。在自动化 Skill 里这很烦人。把上一步的 frontmatter 扩展为:
---
name: release-preflight
description: 发布前自检。
argument-hint: [version]
arguments: [version]
disable-model-invocation: true
allowed-tools:
- Bash(${CLAUDE_SKILL_DIR}/scripts/check.sh)
- Bash(git status *)
- Bash(git log *)
- Bash(git diff *)
- Bash(git tag *)
- Read
- Grep
---
allowed-tools 只授权、不限制。它的语义是"当这个 Skill 激活时,以下工具可免确认使用",而不是"只允许用这些工具"。Claude 仍然可以调用其它工具,只是会走正常的权限流程。
AskUserQuestion),用 disallowed-tools,且该限制在你发送下一条消息时自动清除。settings.json 的 deny 规则里加。.claude/skills/ 下)的 allowed-tools 要在接受工作区信任对话框后才生效——这是为了防止一个被 clone 下来的恶意仓库用 Skill 给自己开后门。所以审查第三方项目的 Skill 时,先看它的 allowed-tools。allowed-tools 写在了 disallowed-tools 思维下,只列了"想用"的,却没意识到还有别的工具也在跑;真正的修复是把脚本调用写成精确的 glob,如 Bash(${CLAUDE_SKILL_DIR}/scripts/check.sh)。/trust 或首次进入时确认。!command`` 注入多维实时数据(进阶)!command`` 的威力在于可以并列多条、可以做格式化、可以跨工具。下面是一个更完整的"PR 评审"风格 Skill,它一次性把 diff、CI 状态、变更文件清单都注入进来:
---
name: pr-xray
description: 对当前 PR 做穿透式评审。用户提到 review PR、看这个 PR、代码评审时使用。
context: fork
agent: Explore
allowed-tools: Bash(gh *) Bash(git *) Read Grep Glob
---
## PR 上下文(运行时注入)
### 变更概览
!`gh pr view --json title,author,additions,deletions,baseRefName,headRefName | jq -r '"标题:\(.title)\n作者:\(.author.login)\n+/-:\(.additions)/\(.deletions)\n分支:\(.baseRefName) ← \(.headRefName)"'`
### 变更文件
!`gh pr diff --name-only`
### CI 状态
!`gh pr checks 2>/dev/null | head -20 || echo "无 CI 检查"`
### 完整 diff(前 400 行)
```!
gh pr diff | head -400
基于上述真实数据,按以下顺序输出: 1. 风险分级:CRITICAL / HIGH / MEDIUM / LOW,各列具体文件:行。 2. 回归点:哪些改动可能破坏既有测试。 3. 遗漏项:有没有改了接口却没改调用方。 只引用上面真实出现过的文件名和行号,禁止臆测未出现的文件。
### 几个易踩的坑
1. **多行命令用代码块**。行内 `!`cmd`` 只适合单行;多行必须用 ` ```! ` 开头的围栏代码块,如上例最后一段。
2. **输出不会被二次扫描**。即一条命令的输出里如果**包含** `!`foo``,它不会被再次执行——这避免了递归注入攻击,但也意味着你不能"用命令生成命令"。
3. **`jq` 在管道里很好用**,但前提是机器装了 `jq`。生产环境建议在 Skill 里加一句 `!`command -v jq || echo "WARN: jq 未安装"`` 做软提示,而不是硬失败。
4. **`context: fork` + `agent: Explore` 的组合**:本 Skill 在一个**隔离的子 Agent** 里运行,它看不到你的对话历史,只有上面这份渲染后的内容作为唯一任务输入。`Explore`/`Plan` 这两个内置 Agent 还会**跳过 CLAUDE.md 和 git status**,所以上下文极小、成本极低,适合"只读分析"类任务。但要注意:fork 模式只适合**有明确任务**的 Skill;如果你的 Skill 只是"一些约定规范"没有任务,子 Agent 收到规范却无事可做,会直接空转返回。
---
## 五、第四步:理解 Skill 的"生命周期",别让它失效
这是最容易被忽略、却最容易导致"Skill 第一次好用、后面就不管用了"的根因。
### 加载与驻留机制
1. **启动时**:Claude Code 只把**所有 Skill 的 name + description** 装进系统提示(这就是为什么 `description` 的前几个词最关键)。完整 SKILL.md **不加载**——这是渐进披露的成本控制点。
2. **调用时**:你或 Claude 触发后,渲染后的 SKILL.md 作为**单条消息**进入对话,并**在剩余整个会话里常驻**。每一行都是**持续**的 token 成本,所以 SKILL.md 要像 `CLAUDE.md` 一样精炼。
3. **压缩时(auto-compaction)**:当上下文被摘要以腾空间时,Claude Code 会把"每个 Skill 最近一次调用"重新挂回,但**每个只保留前 5,000 token**,且所有重挂的 Skill **共享 25,000 token 总预算**,从最近调用的开始填。**结论:一个会话里调用了很多 Skill 之后,最早调用的可能被整体丢弃。**
### 实战建议
```mermaid
flowchart TD
S1["启动:仅加载 name+description"] --> S2{"用户/Claude 调用?"}
S2 -->|是| S3["渲染 SKILL.md 注入对话<br/>常驻整个会话"]
S2 -->|否| S4["零成本待命"]
S3 --> S5{"上下文触发压缩?"}
S5 -->|是| S6["每个 Skill 留前 5000 token<br/>总计 25000 token 预算"]
S6 --> S7["从最近向前填<br/>老的可能被丢弃"]
S5 -->|否| S8["原样保留"]
S7 --> S9{"行为似乎失效?"}
S9 -->|是| S10["重新 /调用一次该 Skill<br/>恢复完整内容"]
S9 -->|否| S11["继续工作"]
落地动作:
reference.md 并在主文件里用链接引用("完整 API 见 reference.md"),按需加载。description 和指令,或干脆重新 /invoke 一次把完整内容刷回来。skillOverrides 统一治理团队 Skill当你不想改某个 Skill 的源文件(比如它在共享仓库里、由 MCP 服务器提供),却想控制它的可见性时,用 /skills 菜单(选中后按空格切换状态)或直接写 settings.local.json:
{
"skillOverrides": {
"legacy-context": "name-only",
"deploy": "off"
}
}
四种状态语义:
| 状态 | 是否给 Claude 列出 | 是否出现在 / 菜单 | 适用场景 |
|---|---|---|---|
on(默认) |
名称+描述 | 是 | 正常使用 |
name-only |
仅名称 | 是 | 描述太吵、想降噪但保留手动调用 |
user-invocable-only |
隐藏 | 是 | 只允许人调用、不让模型自动触发 |
off |
隐藏 | 隐藏 | 完全停用 |
注意:skillOverrides 不影响插件 Skill(用 name-only 形式),插件 Skill 走 /plugin 管理。
release-preflight 完整可运行项目把前面碎片整合成一个端到端可跑的示例。目录结构:
~/.claude/skills/release-preflight/
├── SKILL.md # 入口:参数 + 注入 + 授权
├── scripts/
│ └── check.sh # 可执行脚本:测试+lint+CHANGELOG校验
└── reference.md # 详细检查规则(按需加载,常驻预算外)
scripts/check.sh(记得 chmod +x)#!/usr/bin/env bash
# 发布前自检脚本。退出码非0表示有阻塞项。
set -euo pipefail
VERSION="${1:?用法: check.sh <version>}"
ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
cd "$ROOT"
fail=0
echo "== 测试 =="
if [ -f package.json ]; then npm test --silent || fail=1; fi
if [ -f Makefile ] && make -n test >/dev/null 2>&1; then make test || fail=1; fi
if [ -f pytest ] || [ -d tests ]; then python3 -m pytest -q 2>/dev/null || true; fi
echo "== Lint =="
if [ -f package.json ] && grep -q '"lint"' package.json; then npm run lint --silent || fail=1; fi
echo "== CHANGELOG 校验 =="
if ! grep -q "$VERSION" CHANGELOG.md 2>/dev/null; then
echo "WARN: CHANGELOG.md 未发现 $VERSION"
fi
echo "== 工作区 =="
if [ -n "$(git status --porcelain)" ]; then
echo "WARN: 存在未提交改动"
fi
exit $fail
reference.md(按需加载的细则)# 发布检查细则
## 阻塞项(必须修复才能发版)
- 任一测试失败
- 存在 console.log / debugger / TODO(发布前)
- 接口签名变更但未更新调用方
## 建议项(可带 warning 发版)
- CHANGELOG 未更新
- 新增依赖未在 README 说明
SKILL.md 里挂引用在原 frontmatter 下追加一段:
## 细则参考
完整阻塞/建议项判定见 [reference.md](reference.md),只在需要判定边界情况时读取。
# 1. 赋权
chmod +x ~/.claude/skills/release-preflight/scripts/check.sh
# 2. 进入任意 git 仓库
cd ~/my-project
# 3. 调用
# 在 Claude Code 里输入:
/release-preflight v1.4.0
Claude 会:① 把 v1.4.0 填入 $version;② 自动跑 git status/git log 注入快照;③ 调用 check.sh(已预授权,不弹窗);④ 输出"可发版/阻塞项列表"。整个过程零额外确认、数据全部来自现网、可复现。
flowchart TB
subgraph 编写期["编写期(开发者)"]
D1["SKILL.md<br/>frontmatter + 正文"]
D2["scripts/*.sh<br/>可执行脚本"]
D3["reference.md<br/>按需加载细则"]
end
subgraph 运行期["运行期(Claude Code)"]
R1["/release-preflight v1.4.0"] --> R2["参数替换<br/>\$version = v1.4.0"]
R2 --> R3["上下文注入<br/>git/gh 命令预执行"]
R3 --> R4["allowed-tools<br/>免确认授权生效"]
R4 --> R5["渲染结果作为<br/>单条消息注入"]
end
subgraph 执行期["执行期(Claude)"]
R5 --> E1["调用 check.sh<br/>\${CLAUDE_SKILL_DIR}"]
E1 --> E2["按需 Read reference.md"]
E2 --> E3["输出结论"]
end
subgraph 生命周期["会话生命周期"]
E3 --> L1["SKILL.md 常驻"]
L1 --> L2{"压缩触发?"}
L2 -->|是| L3["留前5000 token<br/>总预算25000"]
L2 -->|否| L4["原样保留"]
end
D1 -.-> R1
D2 -.-> E1
D3 -.-> E2
把你的 Skill 拿来对照这 8 条,过线了就是一个"工程级"Skill:
description 第一句话写清"做什么 + 什么时候用",前 100 字包含触发词。deploy/commit/send-*)加 disable-model-invocation: true。!command`` 预注入,而不是让 Claude 现场猜。${CLAUDE_SKILL_DIR} 引用,不用相对路径。allowed-tools 用精确 glob 授权,只授权不限制;要限制用 disallowed-tools。reference.md 用链接引用。/invoke 一次刷回完整内容。code.claude.com/docs/en/skills,字段表与生命周期描述均来自此处,权威性最高。skill-development SKILL.md 模板(github.com/anthropics/claude-code 仓库 plugins/plugin-dev/skills/skill-development/):Anthropic 自己写 Skill 时遵循的内部规范,是最权威的"参考实现"。claude.com/docs/skills/overview):Skill 是跨工具开放标准,Claude Code 在其上扩展了调用控制、子 Agent 执行、动态注入。release-preflight 端到端跑通);若加入"团队治理 + skillOverrides 实操"模块,扩展到 半天(3–4 小时) 工作坊。db-migrate-check、api-contract-diff、i18n-scan),当场可在真实仓库跑通。${CLAUDE_SKILL_DIR} 正确引用脚本、能否用 !command`` 注入至少 2 维实时数据、能否处理压缩后 Skill 失效的情况。!command` 注入语义、allowed-tools/disallowed-tools` 行为、Skill 内容生命周期(5000/25000 token 预算)均出自此处。[!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 等场景全覆盖。
