Agent SDK 中 Skills 能否被识别,由 setting_sources 与 allowed_tools 这两个开关共同决定:前者负责把 ~/.claude/skills/ 与项目目录下的 .claude/skills/ 拉进会话上下文,后者负责把 Skill 工具挂到工具列表上让模型能调用。生产环境曾因只设其中一个导致”目录里有 SKILL.md 却始终不触发”,后排查发现两个开关必须同时显式打开,缺一会让整条发现链路断在中间。
一、两个配置项各管什么
setting_sources 决定 SDK 是否扫描文件系统。默认值为空数组(Python 端 setting_sources=[],TypeScript 端 settingSources: []),意味着即使本机存在 ~/.claude/skills/pdf-helper/SKILL.md,也不会被加载。这一项对应”加载来源”的开关。
allowed_tools 决定工具栏里是否包含 Skill 这一项。即便文件系统已被扫描,若 allowed_tools=["Read", "Write"] 中没有 Skill,模型依然无法触发任何 skill。这一项对应”调用权限”的开关。
两项缺一会表现为不同的现象:缺 setting_sources 时 What Skills are available? 始终返回空;缺 allowed_tools 时 skill 被索引进 system prompt 但模型报告”tool not available”。
| 配置项 | 控制范围 | 常见值 | 缺省时表现 |
|---|---|---|---|
| setting_sources | 是否扫描 ~/.claude/skills/、<cwd>/.claude/skills/ |
["user", "project"] |
目录里的 SKILL.md 一律不加载 |
| allowed_tools | 工具栏是否含 Skill |
["Skill", "Read", "Bash"] |
skill 存在但无法被调用 |
| cwd | 扫描的根目录 | /path/to/project |
路径错则加载了别的目录的 skill |
| skills | 进一步筛选已发现的 skill | "all" / ["pdf-helper"] / [] |
不影响发现,只影响”启用哪些” |
二、显式设置 settingSources 却遗漏它们的失败模式
调用方常以为”只要打开 setting_sources 就完事”,但下面三类遗漏会分别造成链路断裂:
- 遗漏
Skill工具:allowed_tools不含"Skill",skill 出现在 system prompt 的元信息里,模型却无法调用; - 遗漏
cwd:扫描根目录指向空目录或非项目目录,结果是”加载了但加载的不是预期那一套”; - 遗漏
skills过滤维度:希望只启用部分 skill 时没有传入skills="all"或具名列表,导致所有已发现的 skill 都进入候选池,与按需加载的初衷相违。
把 Python 与 TypeScript 两端的最小正确配置放在一起,方便对照:
from claude_agent_sdk import query, ClaudeAgentOptions
options = ClaudeAgentOptions(
cwd="/path/to/project", # 1. 指向含 .claude/skills/ 的目录
setting_sources=["user", "project"], # 2. 打开 user 与 project 两个来源
skills="all", # 3. 启用所有已发现的 skill
allowed_tools=["Read", "Write", "Bash", "Skill"], # 4. 把 Skill 工具挂上去
)
import { query } from "@anthropic-ai/claude-agent-sdk";
for await (const message of query({
prompt: "Help me process this PDF document",
options: {
cwd: "/path/to/project",
settingSources: ["user", "project"],
skills: "all",
allowedTools: ["Read", "Write", "Bash", "Skill"],
},
})) { console.log(message); }
三、按序排查的四个步骤
当 skill “装了不触发”时,按下面顺序排查比随机猜测快得多:
- 核
setting_sources:打印当前 options,确认值是["user", "project"]或包含["user"]/["project"]至少一个; - 核
allowed_tools:确认字符串"Skill"出现在列表里,区分大小写; - 核
cwd:用ls .claude/skills/*/SKILL.md与ls ~/.claude/skills/*/SKILL.md验证两个目录都存在内容; - 核
skills过滤:未显式声明时部分 SDK 版本默认行为不一致,显式传skills="all"比依赖默认更稳。
四、SKILL.md 元信息如何被消费
加载阶段只把每个 skill 的 name 与 description 注入 system prompt(”渐进式披露”),正文不读。当用户请求的语义与某条 description 匹配,模型才读取该 SKILL.md 正文,再按需打开其附属脚本或模板。这意味着 SKILL.md 的 description 字段决定了”会不会被触发”——描述越具体,被误触或漏触的概率越低。
五、SKILL.md 元信息如何被消费
加载阶段只把每个 skill 的 name 与 description 注入 system prompt(”渐进式披露”),正文不读。当用户请求的语义与某条 description 匹配,模型才读取该 SKILL.md 正文,再按需打开其附属脚本或模板。这意味着 SKILL.md 的 description 字段决定了”会不会被触发”——描述越具体,被误触或漏触的概率越低。
把 description 写好需要三条经验。其一,触发场景要给出明确的”何时使用”提示,例如”处理 PDF 表单填充时调用”比”处理 PDF 时调用”命中率高很多;其二,区分”做什么”和”不做什么”,避免描述与同名功能冲突时模型走向错误分支;其三,把支持的输入输出格式写在 description 里,让模型在调用前就能判断参数对齐。
六、多环境差异:云端与本地
云端 Claude Code 会话(Web、Routines)与本地 ~/.claude/skills/ 目录的可见性不同。云端环境只能加载会话内显式上传或由平台注入的 skill,宿主机的 skills 默认不可见。SDK 集成时要区分两套配置:本地集成走 setting_sources=["user", "project"],云端部署则需要走”plugin 路径”或显式 skills 列表把希望启用的 skill 拉进会话。把”本地能跑、云端就跑不通”的问题归因到这一层差异,可以少走很多弯路。
七、易错点小结
把 skill 路径写进 allowed_tools 是常见误区。allowed_tools 是工具白名单,不是 skill 白名单;后者由 skills 参数或 SKILL.md 内的 name 控制。另一个误区是同时配置 setting_sources=["user", "project"] 与 cwd 指向 /tmp,结果只加载了 user 目录的 skill,项目的 .claude/skills/ 被静默跳过。生产环境建议把 cwd 与 setting_sources 视为绑定参数,修改一个就同步核对另一个。
到这里,Skills 从”写在硬盘上”到”被模型真正调用”的完整链路就清楚了:filesystem → settingsources 扫描 → skill 元信息进 system prompt → 用户请求匹配 description → allowedtools 允许调用 Skill 工具 → 正文与附属文件按需加载。任一环缺位都会让链路在最靠近断点的位置停住。
常见问题(FAQ)
Q1:只设 settingsources 不设 allowedtools 会怎样?
skill 元信息会进 system prompt,但模型调用时会报 tool not available,等于”看得见用不了”。
Q2:project 来源与 user 来源冲突时谁优先?
两者是叠加而非覆盖,project 目录的 skill 会追加在 user 之后,重名时由后加载的覆盖先加载的。
Q3:skill 能不能用编程方式注册?
不能,SDK 明确不提供程序化注册接口,Skill 必须是文件系统工件。