1. 为什么你的 Claude Code Skills 总是"触发不了"或"参数丢了"
如果你已经在 Claude Code 里写过几个 Skill,大概率遇到过这两种情况:一是明明装好了技能,模型却像没看见一样,死活不自动调用;二是敲了/my-skill 参数,结果模型回复里压根没用到你传进去的值。这两个问题看起来是"玄学",其实都指向同一套机制——SKILL.md 的参数传递与上下文预注入。
Claude Code Skills 是 Anthropic 在 Claude Code 里引入的技能复用机制,本质是把一段可复用的提示词、脚本、参考资料打包成一个目录,让模型在合适的时候自动加载。它适合谁?适合那些每天重复写同样提示词的开发者,比如"按团队规范 review PR""生成符合约定的 commit message""检查某个文件的安全问题"。把这些固化成 Skill,你就不用每次重新交代背景。
但 Skill 真正难的地方不在"写提示词",而在于理解它的运行时行为:哪些内容在模型看到之前就已经被填好了,哪些是调用时才传进去的。这篇就围绕 SKILL.md 的配置骨架,把参数传递($ARGUMENTS、$1)和上下文预注入(!命令、@文件、${CLAUDE_PLUGIN_ROOT})拆开讲,每个片段都能直接复制去用。同时我会说明怎么通过统一的 Key/API 通道 TaoToken 完成接入和调用验证,避免你在多个 Key 之间来回切换。
2. 前置准备:用 TaoToken 统一 Key 与 API 通道
在动手写 SKILL.md 之前,先把调用通道理顺。Claude Code 需要能访问模型 API,如果你手上有多个来源的 Key,管理起来很麻烦。TaoToken 提供统一的 Key/API 通道,官网入口是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 基址是 https://taotoken.net/api (这个地址不加 UTM 参数)。
你需要做的准备只有三步:
第一步,在控制台创建一个 API Key。打开 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,登录后进入 API Keys 页面,新建一个 Key 并复制保存。这个 Key 就是后面 Claude Code 调用时要用的凭证。
第二步,确认你要用的模型。可以在模型对话页面 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 先试跑一句,确认通道正常、模型可用,再去配置 Claude Code。
第三步,把 Key 和 API 基址写进 Claude Code 的环境变量或配置里。Claude Code 读取的是ANTHROPIC_BASE_URL和ANTHROPIC_API_KEY这类变量,把基址指向 TaoToken 的 API 地址,Key 填你刚创建的那个即可。
注意:API 基址用
https://taotoken.net/api,不要带任何查询参数;官网链接才带 UTM。两者别混。
如果你打算长期在 Claude Code 里跑编码任务、Agent 流程,建议直接看 Coding Plan https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,它针对编码场景做了额度规划,比按次调用更省心。接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,遇到字段不清楚可以对照。
3. SKILL.md 配置骨架:三级加载与 frontmatter 字段
先把心智模型立住:Skill 就是一张"会自动填好的任务纸条"。纸条上的字大部分是固定的,但可以留两类空——一类等你调用时临时告诉它(参数传递),一类让它自己去查了填上(上下文预注入)。无论哪种空,填空动作都发生在模型读到之前,模型拿到的是成品,它甚至不知道原文里有过占位符。
这个"填空"分三级渐进式加载:
| 层级 | 内容 | 何时进入上下文 | 体量 |
|---|---|---|---|
| L1 元数据 | name + description | 会话一开始就注入,始终在场 | 约 100 词 |
| L2 正文 | SKILL.md 主体 | 技能被触发时才注入 | 建议 1.5k–2k 词 |
| L3 资源 | references/ scripts/ assets/ | 模型按需读取/执行 | 近乎无限 |
L1 是真正的"预注入":你装的每个 Skill,它的 name 和 description 在对话还没开始时就写进了系统提示,这是模型判断"该不该自动调用"的唯一线索。所以 description 必须写成第三人称加具体触发短语:
# 好:模型能准确判断何时触发 description: This skill should be used when the user asks to "create a hook", "add a PreToolUse hook", or mentions hook events. # 差:太笼统,技能几乎不会被自动触发 description: Provides guidance for working with hooks.frontmatter 里控制参数与注入行为的字段如下:
--- name: pr-check description: Review PR against project checklist when user asks to "check PR" or "review pull request" argument-hint: [pr-number] [priority] [assignee] allowed-tools: Read, Bash(git:*), Bash(gh:*) model: sonnet disable-model-invocation: true context: fork agent: Explore ---逐个说明:name/description是 L1 预注入的全部内容;argument-hint只是自动补全和/help里的说明书,不参与实际替换;allowed-tools限定可用工具,用!注入命令时必须放行对应命令,比如Bash(git:*);model可覆盖执行模型;disable-model-invocation: true表示仅用户可调、模型不能自动触发,适合部署、发送这类有副作用的操作;user-invocable: false表示仅模型可调、用户看不到,适合纯背景知识;context: fork让技能在隔离子代理中运行,不污染主会话。
调用权限一览:
| 设置 | 用户可调 | 模型可调 | 用途 |
|---|---|---|---|
| 默认 | 是 | 是 | 通用技能 |
| disable-model-invocation: true | 是 | 否 | 有副作用的操作 |
| user-invocable: false | 否 | 是 | 后台知识 |
4. 上下文预注入:!命令、@文件与插件路径
上下文预注入的核心是"让纸条自带背景信息"。当技能被触发、L2 正文加载时,正文里可以嵌入三种会被实时替换的写法。
第一种是!命令,注入命令的实时输出:
## 当前状态 - 分支: !`git branch --show-current` - 改动: !`git status --short`运行时会先执行这些命令,把标准输出内联进正文。模型看到的不是那句git branch,而是已经变成- 分支: main的成品。这就是"预注入"最直白的体现:命令在模型接手前就跑完了。用!时,frontmatter 里要用allowed-tools: Bash(git:*)之类放行对应命令。
第二种是@文件,注入文件内容:
Review @src/api/users.ts for potential bugs.@让运行时先把文件读进来再交给模型。它还能和参数组合成@$1,表示"读取用户传进来那条路径所指的文件"。
第三种是${CLAUDE_PLUGIN_ROOT},插件内的可移植路径:
Run: !`node ${CLAUDE_PLUGIN_ROOT}/scripts/analyze.js`插件型 Skill 专用,自动解析为插件的绝对路径,用来引用插件自带的脚本或模板,避免硬编码。
把三者串起来看一次完整时序。以官方 pr-check 技能为例:
--- name: pr-check description: Review PR against project checklist disable-model-invocation: true context: fork --- ## PR Context - Diff: !`gh pr diff` - Description: !`gh pr view` Review against [checklist.md](checklist.md). For each item, mark or with explanation.调用/pr-check时,运行时的处理流水线是:先做参数展开(替换$ARGUMENTS/$1,没有则末尾追加),再执行命令注入(跑gh pr diff、gh pr view并内联输出),然后解析@文件与${CLAUDE_PLUGIN_ROOT},最后把一张"已物化"的纯文本注入上下文。因为context: fork,它进入独立子代理,模型开始工作时只看到成品,看不到任何占位符。
5. 运行时参数传递:$ARGUMENTS、$1与兜底规则
Skill 有两个调用入口,但共享同一套参数机制:用户显式调用(敲/skill-name 参数)和模型自动调用(根据 L1 描述判断相关后自行调用并传入参数)。一个关键事实是,传统的.claude/commands/*.md和新的.claude/skills/<name>/SKILL.md运行时加载方式完全一样,只是文件布局不同,所以下面的参数写法对两者通用。
三种占位符写法:
# $ARGUMENTS:全部参数当作一整个字符串 Fix issue #$ARGUMENTS following our coding standards. # /fix-issue 123 → Fix issue #123 following our coding standards. # $1 $2 $3:位置参数,分别对应第 1、2、3 个 Review PR #$1 with priority $2, then assign to $3. # /review-pr 123 high alice → Review PR #123 with priority high, then assign to alice. # 混合:前几个用位置,剩下的打包 Deploy $1 to $2 with options: $3 # /deploy api staging --force --skip-tests → Deploy api to staging with options: --force --skip-tests有一条几乎没人注意的兜底规则:如果 SKILL.md 里根本没写$ARGUMENTS,运行时会把参数以ARGUMENTS: <值>的形式追加到内容末尾。也就是说参数永远不会丢,区别只在于——写了占位符,参数被精确插到指定位置;没写占位符,参数被兜底追加到结尾,交由模型自行理解。
再强调一次:argument-hint只是说明书,不参与传参。真正的传参靠$ARGUMENTS/$N。
还有一种风格值得对照:很多实用 Skill 几乎不用占位符,而是把一连串动作写成自然语言指令,让模型运行时自己去调工具收集上下文。比如一个提交流程 Skill,它不预先传"要提交哪些文件",而是在正文里指挥模型"先跑git status/git diff分析改动,再分组生成 commit message"——参数是模型在运行中动态产出的,不是调用时传入的。这说明参数传递不是必需品,"正文指令 + 模型自主收集"往往比硬塞参数更灵活。
6. 验证请求:确认参数与预注入真的生效
配置写完,必须验证。最直接的方式是造一个最小 Skill,把参数和注入都放进去,然后调用看输出。
在.claude/skills/echo-test/SKILL.md写入:
--- name: echo-test description: Use when user asks to "test skill params" or "verify skill injection" argument-hint: [name] [env] allowed-tools: Bash(git:*) --- ## 参数验证 - 第一个参数: $1 - 第二个参数: $2 - 全部参数: $ARGUMENTS ## 上下文预注入验证 - 当前分支: !`git branch --show-current`然后在 Claude Code 里调用:
/echo-test alice staging预期结果:模型回复里第一个参数显示alice,第二个参数显示staging,全部参数显示alice staging,当前分支显示你仓库的真实分支名。如果分支那行还是原样的!git branch --show-current``,说明allowed-tools没放行Bash(git:*),或者命令执行失败。
再验证兜底规则:把上面 SKILL.md 里的$1/$2/$ARGUMENTS全删掉,只留正文,再调用/echo-test alice staging。你应该在模型看到的正文末尾发现ARGUMENTS: alice staging被追加进来。这一步能帮你确认"参数不会丢"这条规则确实生效。
验证模型通道是否正常,可以先用模型对话页面 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 跑一句简单请求,确认返回正常,再回到 Claude Code 里测 Skill。如果 Skill 调用报鉴权错误,去 API Keys 页面 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 检查 Key 是否有效、是否复制完整。
7. 本篇常见错误排查
技能不自动触发。九成是 description 写得太笼统。模型对未触发的技能只看得见 L1 描述,所以要写成第三人称加具体触发短语,比如"当用户说'检查 PR'或'review pull request'时使用",而不是"处理提交相关事务"。
参数没被替换。检查占位符拼写:是$ARGUMENTS不是$ARGUMENT,是$1不是$01。另外确认你调用时确实带了参数,/my-skill后面空着,$1自然为空。
!命令没执行或报权限错误。frontmatter 里必须用allowed-tools放行对应命令,比如Bash(git:*)、Bash(gh:*)。只写Bash有时不够精确,建议带上命令前缀。
@文件读不到。路径要相对仓库根目录,或者用@$1让用户传绝对/相对路径。文件不存在时运行时不会报错,只是注入为空,模型会以为文件是空的。
${CLAUDE_PLUGIN_ROOT}解析成字面量。这个变量只在插件型 Skill 里有效,普通.claude/skills/目录下的 Skill 用不了,会原样输出。普通 Skill 直接用相对路径。
占位符替换不可逆。!cmd一旦跑完被内联,模型无法"重跑";要拿最新状态,只能靠下一次调用重新注入。`$ARGUMENTS` 是纯文本插值,不做校验,需要校验就在正文里显式写,比如用 `!`echo "$1" | grep -E ...验证环境名。
把所有东西堆进 SKILL.md。那会破坏 L2 的精简性,每次触发都白灌一堆上下文。细节挪到references/,靠指针按需加载(L3)。
还在用.claude/commands/。它是 legacy,两者加载行为一致,但目录格式能捆绑references/scripts/assets,才能发挥完整的渐进式披露能力。新技能优先用 SKILL.md 目录格式。
8. 把动态填充做成模型无感的预处理层
Claude Code 的 Skill 之所以强大,不在于"写了一段提示词",而在于它把动态填充做成了模型无感的预处理层。上下文预注入让技能自带实时背景——三级加载控制"何时进上下文",!/@/${CLAUDE_PLUGIN_ROOT}控制"注入什么内容";运行时参数传递让技能接受临时输入——$ARGUMENTS/$N精确插值,外加"末尾追加"的兜底。两者殊途同归:在模型读到之前,把一张模板纸条填成成品。
想清楚"哪些空由用户填、哪些空由纸条自己查",你就能设计出真正好用的 Skill。如果你还在为多 Key 管理头疼,直接用 TaoToken 的统一通道接入,把精力留给 Skill 设计本身。长期跑编码和 Agent 任务的话,Coding Plan https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 的额度规划会比零散调用更稳。接入细节对照文档 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 即可,遇到鉴权问题先查 API Keys 页面。