1. 从一次技能不生效说起:Claude Code skills 加载链路到底怎么走
如果你在 Claude Code 里写过自定义技能,大概率遇到过这种场景:.claude/skills/目录下明明放了SKILL.md,输入/你的技能名却提示Unknown skill,或者技能能被识别但模型完全没按你写的步骤执行。这类问题十有八九不是模型不听话,而是技能压根没被加载进 Command 注册表,或者加载了但触发条件没命中。
Claude Code 的 skills 机制本质上是一套「磁盘 Markdown → 内存 Command 对象 → 注入对话上下文」的编译链路。理解这条链路,你才能知道为什么有的技能启动就能用、有的要等文件被打开才激活、有的同名技能被静默覆盖。这篇聚焦加载阶段和斜杠命令触发阶段,把SKILL.md的解析、Command 的注册、getPromptForCommand的延迟编译讲清楚,同时给出把 endpoint 切到 TaoToken 后的完整验证动作,确保你自定义的技能能被正确加载和调用。
适合人群:已经会用 Claude Code 但想搞懂 skills 内部机制的开发者、需要给团队沉淀可复用能力包的工程同学、以及想排查「技能不生效」这类玄学问题的同学。读完之后你应该能自己画出加载流程图,并且能定位 90% 的技能加载失败原因。
先说结论性的认知:一个 skill 就是一个文件夹,核心是SKILL.md,它由 YAML frontmatter 加正文组成。frontmatter 决定「什么时候加载、谁能调用、用什么模型」,正文决定「模型看到什么指令」。加载阶段只解析 frontmatter 并构建 Command 对象,正文被闭包捕获,直到你真正调用/skill-name时才编译成 prompt。这个「延迟加载」设计是理解整条链路的关键。
2. TaoToken 前置准备:让 Claude Code 的请求走对 endpoint
在深入源码之前,得先把运行环境搭好,否则你改了技能也不知道请求发到哪去了。Claude Code 默认请求 Anthropic 官方 endpoint,我们要做的是把 base URL 指向 TaoToken,这样后续验证技能加载时,请求链路是可控、可观测的。
TaoToken 在这里扮演的是模型 API 接入层,官网入口是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 地址是 https://taotoken.net/api 。你需要先去控制台创建一个 API Key,控制台地址 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite ,Key 管理页在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 。创建完记得复制保存,Key 只显示一次。
Claude Code 读取配置的方式主要有两种:环境变量和 settings 文件。环境变量方式最直接,适合临时验证:
export ANTHROPIC_BASE_URL="https://taotoken.net/api" export ANTHROPIC_AUTH_TOKEN="sk-你的TaoToken密钥" export ANTHROPIC_MODEL="claude-sonnet-4-6"如果你希望长期生效,写进~/.claude/settings.json更稳妥。注意这个文件是 Claude Code 的全局设置,不是项目级的:
{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_AUTH_TOKEN": "sk-你的TaoToken密钥", "ANTHROPIC_MODEL": "claude-sonnet-4-6" } }这里有个容易踩的坑:ANTHROPIC_AUTH_TOKEN和ANTHROPIC_API_KEY是两个不同的变量,Claude Code 优先读ANTHROPIC_AUTH_TOKEN。如果你两个都设了,可能出现 Key 冲突导致 401。建议只保留ANTHROPIC_AUTH_TOKEN。
配置完成后,用一条最简单的请求验证连通性。Claude Code 本身没有独立的 ping 命令,但你可以直接跑一个非交互式请求:
claude -p "回复 ok 两个字母即可" --model claude-sonnet-4-6如果返回ok,说明 endpoint 和 Key 都通了。如果报 401,先检查 Key 是否复制完整、有没有多余空格;如果报连接超时,检查ANTHROPIC_BASE_URL是不是写成了带路径的https://taotoken.net/api/v1,Claude Code 会自动拼接路径,多写/v1反而会 404。
模型 ID 这块,TaoToken 支持 Claude 系列模型,常用的有claude-sonnet-4-6、claude-opus-4-6等。你可以在模型对话页 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite 先手动试一下模型是否可用,确认没问题再写进配置。这一步别省,我见过太多人配置写错却以为是技能加载的问题。
3. 可复制配置:SKILL.md 目录结构与 frontmatter 全字段
现在进入正题。Claude Code 加载技能时,会按固定优先级扫描多个目录。理解目录结构是第一步,因为放错位置技能就不会被加载。
技能目录的搜索范围,优先级从高到低是这样的:
/etc/claude/.claude/skills/ ← 企业策略管理(managed) ~/.claude/skills/ ← 用户全局技能(user) <项目>/.claude/skills/ ← 项目级技能(project,沿目录树向上搜索) --add-dir 指定目录/.claude/skills/ ← 额外目录(additional) <项目>/.claude/commands/ ← 旧格式命令(legacy,向后兼容)注意一个细节:项目级技能会沿目录树向上搜索。也就是说你在/home/user/project/src/components/下工作时,Claude Code 会从当前目录一路往上找.claude/skills/,直到项目根。这意味着你可以在子目录放技能,但同名技能会被更靠近根目录的覆盖(先注册者胜出)。
一个标准的技能目录长这样:
.claude/skills/ └── commit-helper/ ├── SKILL.md ← 必需,指令文件 ├── scripts/ │ └── format.py ← 可选,脚本 └── references/ └── style.md ← 可选,参考资料关键约束:只支持skill-name/SKILL.md这种两级格式。如果你直接把SKILL.md放在.claude/skills/根目录下,不会被识别。加载逻辑里明确跳过了非目录项。
接下来是SKILL.md的 frontmatter 全字段。这是加载阶段真正被解析的部分:
--- name: commit-helper description: 生成符合 Conventional Commits 规范的提交信息 argument-hint: <改动描述> arguments: [scope, message] model: claude-sonnet-4-6 effort: high context: inline allowed-tools: [Bash, Read, Write] user-invocable: true disable-model-invocation: false paths: src/**/*.ts, src/**/*.tsx hooks: PreToolUse: - command: "eslint $FILE" matcher: "Write|Edit" shell: bash version: "1.0" --- # Commit Helper 当用户需要生成提交信息时,按以下步骤执行: 1. 运行 `git diff --staged` 查看暂存区改动 2. 分析改动类型(feat/fix/docs/refactor) 3. 生成符合规范的提交信息 4. 使用 `$ARGUMENTS` 中的描述作为补充 注意事项:不要自动执行 git commit,只生成信息供用户确认。逐个字段说明。name是显示名称,可选,默认取目录名。description最重要,它决定模型能否通过 SkillTool 语义匹配到这个技能,写不好模型就找不到。argument-hint是参数提示,用户输入/commit-helper时会显示。arguments定义参数列表。
model和effort控制模型行为,effort可选low/medium/high或整数。context有两个值:inline在主线程执行,fork在独立子进程执行(有自己的上下文和 token 预算)。allowed-tools声明技能执行时额外允许的工具,这个会作为权限声明注入。
user-invocable控制用户能否通过/name调用,disable-model-invocation控制模型能否通过 SkillTool 调用。这两个开关组合出四种调用权限,按需配置。
paths是条件激活字段,这是很多人忽略的机制。带paths的技能不会在启动时激活,而是存进conditionalSkillsMap,等用户操作的文件路径匹配上模式时才激活。匹配用的是 gitignore 风格,底层是ignore库。比如paths: src/**/*.tsx意味着只有当你打开或编辑src下的 tsx 文件时,这个技能才会被加入动态技能列表。
hooks定义钩子,PreToolUse可以在工具执行前跑命令,matcher是工具名正则。shell指定 shell 环境,version是版本号。
这里要强调一个加载顺序的坑:initBundledSkills()必须在getCommands()之前完成。源码里是这么写的:
// 同步注册:必须在 getCommands 之前完成 if (process.env.CLAUDE_CODE_ENTRYPOINT !== 'local-agent') { initBuiltinPlugins(); initBundledSkills(); } // 并行启动 const setupPromise = setup(preSetupCwd, ...); const commandsPromise = worktreeEnabled ? null : getCommands(preSetupCwd);initBundledSkills()是纯内存的数组 push 操作,耗时不到 1ms,但它必须在getCommands()启动前完成,否则getBundledSkills()返回空数组,内嵌技能全部丢失。这个设计说明加载是有严格时序的,不是随便并行。
4. 验证请求:从斜杠命令到模型响应的完整链路
配置写好了,怎么确认技能真的被加载并正确调用?这一节给出可复现的验证步骤。
第一步,确认技能被注册。启动 Claude Code 后输入/会弹出命令列表,你的技能名应该出现在里面。如果没出现,说明加载阶段就失败了,往下看第五节排查。
第二步,触发技能并观察请求。输入/commit-helper 修复登录超时,Claude Code 会走这条链路:
REPL.onSubmit() → handlePromptSubmit() // 判断是否排队 → processUserInput() // 模式路由,命中 slash 分支 → processSlashCommand() // 解析命令名,查注册表 → getMessagesForPromptSlashCommand() // 加载技能内容 → onQuery() // 发送给模型在processSlashCommand这一层,会先解析命令名:
const parsed = parseSlashCommand(inputString) // 输入 "/commit-helper 修复登录超时" 解析为: // { commandName: 'commit-helper', args: '修复登录超时', isMcp: false }然后查注册表,找不到就返回Unknown skill。找到后按 type 分发,技能都是prompt类型,进入getMessagesForPromptSlashCommand()。
第三步,观察技能内容编译。这一步调用command.getPromptForCommand(args, context),它做三件事:变量替换($ARGUMENTS、${CLAUDE_SKILL_DIR}、${CLAUDE_SESSION_ID})、shell 注入执行(!command格式)、返回ContentBlockParam[]。最终构造出的消息列表长这样:
const messages = [ createUserMessage({ content: metadata }), // 命令元数据 createUserMessage({ content: skillContent, isMeta: true }), // SKILL.md 内容 ...attachmentMessages, createAttachmentMessage({ type: 'command_permissions', allowedTools: additionalAllowedTools, }), ] return { messages, shouldQuery: true, allowedTools, model, effort, command }注意isMeta: true,这表示 SKILL.md 内容对用户隐藏,但会作为 user message 发给模型。shouldQuery: true是关键,它告诉上层需要调用模型。
第四步,验证模型响应。如果一切正常,模型会按 SKILL.md 里的步骤执行,比如先跑git diff --staged,再生成提交信息。你可以在 Claude Code 的输出里看到工具调用记录。
如果你想更直接地验证 endpoint 是否走对了,可以在请求发出后去 TaoToken 控制台的用量页面看调用记录。控制台 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite 能看到每次请求的模型、token 消耗、时间戳。如果技能触发了但控制台没有记录,说明请求没走 TaoToken,回去检查环境变量。
一个实测技巧:把effort设成high时,模型对 SKILL.md 里多步骤指令的遵循度明显更好。如果你的技能步骤多、逻辑复杂,别省这个配置。
5. 本篇常见错排查:401、Unknown skill 与技能不激活
这一节对照真实报错,给出排查路径。这些错误我基本都踩过,按顺序排查能省很多时间。
报错一:401 Unauthorized
API Error: 401 {"error":{"type":"authentication_error","message":"invalid x-api-key"}}原因通常是 Key 配错。检查三点:ANTHROPIC_AUTH_TOKEN是否复制完整(TaoToken 的 Key 以sk-开头)、有没有多余空格或换行、是否同时设了ANTHROPIC_API_KEY导致冲突。解决方法是只保留ANTHROPIC_AUTH_TOKEN,清掉其他 Key 变量。如果用的是 settings.json,确认 JSON 格式合法,可以用cat ~/.claude/settings.json | python -m json.tool验证。
报错二:Unknown skill: xxx
Unknown skill: commit-helper这个报错来自processSlashCommand的注册表查找失败。可能原因:技能目录结构不对(必须是skill-name/SKILL.md两级)、SKILL.md文件名大小写不对(必须全大写)、frontmatter 的 YAML 格式错误导致解析失败、技能被同名技能覆盖。排查方法是在 Claude Code 里输入/看命令列表,如果技能不在列表里就是加载失败。检查SKILL.md的 frontmatter 是否以---开头和结尾,中间不能有语法错误。
报错三:技能在列表里但调用无反应
技能能被/列出,但输入后模型没按指令执行。这通常是description写得太模糊,或者disable-model-invocation设成了true导致模型无法通过 SkillTool 调用。另外检查context是不是设成了fork,fork 模式在独立子进程执行,输出可能不在主线程显示。
报错四:条件技能不激活
带了paths字段的技能不会在启动时激活。如果你配了paths: src/**/*.tsx但一直在编辑.ts文件,技能永远不会激活。解决方法是确认你操作的文件路径匹配paths模式,或者干脆去掉paths让它启动即激活。
报错五:local proxy failed / connection refused
Error: connect ECONNREFUSED 127.0.0.1:xxxx这说明请求被转发到了本地代理端口,通常是环境里残留了HTTP_PROXY/HTTPS_PROXY变量。Claude Code 会读取这些变量。解决方法是清掉代理变量:
unset HTTP_PROXY HTTPS_PROXY http_proxy https_proxy然后重新验证claude -p "ok"。
报错六:reading 'choices' of undefined
这个报错通常出现在响应格式不符合预期时,说明 endpoint 返回的不是标准 OpenAI/Anthropic 格式。检查ANTHROPIC_BASE_URL是否写成了https://taotoken.net/api,不要带/v1或/chat/completions后缀。Claude Code 会自己拼接路径。
排查完这些,如果技能还是不生效,用最笨但最有效的办法:把SKILL.md的 frontmatter 精简到只剩description,确认能加载后再逐个加字段。这样能快速定位是哪个字段导致的解析失败。
6. 语义一致 CTA:把技能链路跑通之后
加载链路和触发链路都跑通之后,你会发现 skills 的真正价值在于「知识可维护」——最佳实践集中在SKILL.md里,更新文件就更新了能力,不用改代码。这也是为什么值得花时间搞懂源码:你知道技能在哪个环节被解析、在哪个环节被注入,排查问题时就有据可依。
如果你还没配好 endpoint,先去 TaoToken 控制台 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite 创建 Key,API Key 管理页 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 可以随时查看和轮换。接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,里面有各客户端的配置示例。
想先手动验证模型是否可用,去模型对话页 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite 试一条请求最快。如果你打算长期用 Claude Code 做编码和 Agent 任务,Coding Plan https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite 的额度模型更适合高频调用场景。
下一篇会讲剩下 8 种调用入口:SkillTool 的模型自主调用、MCP Skill、Cron 定时任务、Agent 预加载、Ultraplan 关键字、初始 prompt 注入。这些入口的触发条件和执行模式各不相同,搞懂了就能把技能用在更复杂的自动化流程里。