Extend agents with skills changedagent-sdk/skills
Nearest release: v2.1.302, published 3 hours before upstream edited the page. Shown because the two are within 24 hours of each other. Nothing here says the release caused the edit.
Upstream edited this page at 16 Oct 2026 20:27 UTC, give or take a minute or two: the time comes from Anthropic’s own sitemap rather than from a commit. This site recorded the change at 20 Oct 2026 18:37 UTC.
Upstream edited
Recorded here
Lines+300added
Lines−318removed
From line
19
where the diff opens
First seen
14 Aug 2026
this site's first read of the page
Recorded edits13to this page, all time
The whole hunk
from line 19, old and new numbered
/
The two sides of this change are more than 400 edits apart, too far apart to line up, so this is the differ's own diff of it and the words inside a line are not marked.
from line 19
1919
2020Unlike subagents, which you can define in the [`agents` option](/docs/en/agent-sdk/subagents#programmatic-definition-recommended), you create skills as files on disk. The SDK doesn't provide a programmatic API for registering them.
2121
22<Note>
23 Skills are discovered through the filesystem setting sources. With default `query()` options, the SDK loads user and project sources, so skills in `~/.claude/skills/`, `<cwd>/.claude/skills/`, and `.claude/skills/` in any parent directory of `<cwd>` up to the repository root are available. The project source also covers `<dir>/.claude/skills/` in each directory you pass through `additionalDirectories` (TypeScript) or `add_dirs` (Python), because the SDK passes those directories to Claude Code as [`--add-dir`](/docs/en/skills#skills-from-additional-directories). If you set `settingSources` explicitly, include `'project'` to keep project and added-directory skills and `'user'` to keep your personal skills, or use the [`plugins` option](/docs/en/agent-sdk/plugins) to load skills from a specific path.
24</Note>
22> [!NOTE]
23>
24> Skills are discovered through the filesystem setting sources. With default `query()` options, the SDK loads user and project sources, so skills in `~/.claude/skills/`, `<cwd>/.claude/skills/`, and `.claude/skills/` in any parent directory of `<cwd>` up to the repository root are available. The project source also covers `<dir>/.claude/skills/` in each directory you pass through `additionalDirectories` (TypeScript) or `add_dirs` (Python), because the SDK passes those directories to Claude Code as [`--add-dir`](/docs/en/skills#skills-from-additional-directories). If you set `settingSources` explicitly, include `'project'` to keep project and added-directory skills and `'user'` to keep your personal skills, or use the [`plugins` option](/docs/en/agent-sdk/plugins) to load skills from a specific path.
2525
2626## Use skills with the Agent SDK
2727
from line 29
2929
3030For example, to let Claude invoke only two named skills:
3131
32<CodeGroup>
33 ```python Python theme={null}
34 options = ClaudeAgentOptions(skills=["pdf", "docx"])
35 ```
36
37 ```typescript TypeScript theme={null}
38 const options = { skills: ["pdf", "docx"] };
39 ```
40</CodeGroup>
32```python Python theme={null}
33options = ClaudeAgentOptions(skills=["pdf", "docx"])
34```
35
36```typescript TypeScript theme={null}
37const options = { skills: ["pdf", "docx"] };
38```
4139
4240### Set up skills in a session
4341
from line 45
4745
4846The following example enables every discovered skill in a session and pre-approves the tools that skills commonly need. The example sets `cwd` to the process's current working directory, so run it from inside a project that has a `.claude/skills/` directory in the current directory or any parent up to the repository root:
4947
50<CodeGroup>
51 ```python Python theme={null}
52 import asyncio
53 import os
54
55 from claude_agent_sdk import query, ClaudeAgentOptions
56
57
58 async def main():
59 options = ClaudeAgentOptions(
60 cwd=os.getcwd(), # .claude/skills/ here or in a parent directory
61 setting_sources=["user", "project"], # Load skills from filesystem
62 skills="all", # Let Claude invoke every discovered skill
63 allowed_tools=["Read", "Write", "Bash"],
64 )
65
66 async for message in query(
67 prompt="Help me process this PDF document", options=options
68 ):
69 print(message)
70
71
72 asyncio.run(main())
73 ```
74
75 ```typescript TypeScript theme={null}
76 import { query } from "@anthropic-ai/claude-agent-sdk";
77
78 for await (const message of query({
79 prompt: "Help me process this PDF document",
80 options: {
81 cwd: process.cwd(), // .claude/skills/ here or in a parent directory
82 settingSources: ["user", "project"], // Load skills from filesystem
83 skills: "all", // Let Claude invoke every discovered skill
84 allowedTools: ["Read", "Write", "Bash"]
85 }
86 })) {
87 console.log(message);
48```python Python theme={null}
49import asyncio
50import os
51
52from claude_agent_sdk import query, ClaudeAgentOptions
53
54
55async def main():
56 options = ClaudeAgentOptions(
57 cwd=os.getcwd(), # .claude/skills/ here or in a parent directory
58 setting_sources=["user", "project"], # Load skills from filesystem
59 skills="all", # Let Claude invoke every discovered skill
60 allowed_tools=["Read", "Write", "Bash"],
61 )
62
63 async for message in query(
64 prompt="Help me process this PDF document", options=options
65 ):
66 print(message)
67
68
69asyncio.run(main())
70```
71
72```typescript TypeScript theme={null}
73import { query } from "@anthropic-ai/claude-agent-sdk";
74
75for await (const message of query({
76 prompt: "Help me process this PDF document",
77 options: {
78 cwd: process.cwd(), // .claude/skills/ here or in a parent directory
79 settingSources: ["user", "project"], // Load skills from filesystem
80 skills: "all", // Let Claude invoke every discovered skill
81 allowedTools: ["Read", "Write", "Bash"]
8882 }
89 ```
90</CodeGroup>
83})) {
84 console.log(message);
85}
86```
9187
9288### Confirm skills loaded
9389
from line 116
120116
121117You can dispatch commands that work without an interactive terminal through the SDK. The `system/init` message lists the ones available in your session in its `slash_commands` field. Commands that need an interactive terminal, such as `/theme` and `/terminal-setup`, don't appear in the list. Access the field when your session starts:
122118
123<CodeGroup>
124 ```typescript TypeScript theme={null}
125 import { query } from "@anthropic-ai/claude-agent-sdk";
126
119```typescript TypeScript theme={null}
120import { query } from "@anthropic-ai/claude-agent-sdk";
121
122for await (const message of query({
123 prompt: "Hello Claude",
124 options: { maxTurns: 1 }
125})) {
126 if (message.type === "system" && message.subtype === "init") {
127 console.log("Available commands:", message.slash_commands);
128 }
129}
130```
131
132```python Python theme={null}
133import asyncio
134from claude_agent_sdk import query, ClaudeAgentOptions, SystemMessage
135
136
137async def main():
138 async for message in query(prompt="Hello Claude", options=ClaudeAgentOptions(max_turns=1)):
139 if isinstance(message, SystemMessage) and message.subtype == "init":
140 print("Available commands:", message.data["slash_commands"])
141
142
143asyncio.run(main())
144```
145
146The printed list mixes built-in commands, bundled skills, your user-invocable skills, and `.claude/commands/` files:
147
148```text theme={null}
149Available commands: ["clear", "compact", "context", "usage", "code-review", "verify", "security-check", ...]
150```
151
152A skill with [`user-invocable: false`](/docs/en/skills#control-who-invokes-a-skill) in its frontmatter doesn't appear in this list or in the `skills` array from [Confirm skills loaded](#confirm-skills-loaded). Sessions that configure [MCP servers](/docs/en/agent-sdk/mcp) can also expose [MCP prompts as commands](/docs/en/mcp#use-mcp-prompts-as-commands).
153
154### Dispatch commands by name
155
156Send a command by including it in your prompt string, the same way you send regular text. Dispatch doesn't depend on the `skills` option. Sending `/<name>` runs a user-invocable skill even when your `skills` list omits it. Commands that act on conversation history, such as `/compact`, need prior messages to work with.
157
158A `/<name>` that matches neither a command in the session nor a built-in Claude Code command doesn't fail the query. Claude Code sends the prompt to Claude as an ordinary message, with a note that the command didn't run, so the query spends a model turn and returns Claude's reply. Before v2.1.274, a `/<name>` that matched nothing returned `Unknown command: /<name>` as the result without a model turn.
159
160A `/<name>` that matches a built-in Claude Code command that isn't available in the session, such as `/theme`, returns `/theme isn't available in this environment.` as the result without a model turn.
161
162> [!NOTE]
163>
164> A command can hit the `maxTurns` / `max_turns` limit like any other prompt, ending the query with an error result instead of `success`. For the error-result contract, see [Handle the result](/docs/en/agent-sdk/agent-loop#handle-the-result). If your command might hit the limit, wrap the loop in a `try`/`catch` in TypeScript or `try`/`except` in Python, as shown in [Single Message Input](/docs/en/agent-sdk/streaming-vs-single-mode#single-message-input), or set `maxTurns` high enough for the work to complete.
165
166### Compact history with `/compact`
167
168The `/compact` command reduces the size of your conversation history by summarizing older messages while preserving important context. Compaction needs an existing conversation with enough prior messages to summarize. This example has a conversation first, then compacts it and reads the `compact_boundary` system message that reports the result:
169
170```typescript TypeScript theme={null}
171import { query } from "@anthropic-ai/claude-agent-sdk";
172
173// Compaction needs existing history, so have a conversation first
174try {
127175 for await (const message of query({
128 prompt: "Hello Claude",
129 options: { maxTurns: 1 }
176 prompt: "Explain what this project does",
177 options: { maxTurns: 2 }
130178 })) {
131 if (message.type === "system" && message.subtype === "init") {
132 console.log("Available commands:", message.slash_commands);
179 if (message.type === "result" && message.subtype === "success") {
180 console.log(message.result);
133181 }
134182 }
135 ```
136
137 ```python Python theme={null}
138 import asyncio
139 from claude_agent_sdk import query, ClaudeAgentOptions, SystemMessage
140
141
142 async def main():
143 async for message in query(prompt="Hello Claude", options=ClaudeAgentOptions(max_turns=1)):
144 if isinstance(message, SystemMessage) and message.subtype == "init":
145 print("Available commands:", message.data["slash_commands"])
146
147
148 asyncio.run(main())
149 ```
150</CodeGroup>
151
152The printed list mixes built-in commands, bundled skills, your user-invocable skills, and `.claude/commands/` files:
153
154```text theme={null}
155Available commands: ["clear", "compact", "context", "usage", "code-review", "verify", "security-check", ...]
156```
157
158A skill with [`user-invocable: false`](/docs/en/skills#control-who-invokes-a-skill) in its frontmatter doesn't appear in this list or in the `skills` array from [Confirm skills loaded](#confirm-skills-loaded). Sessions that configure [MCP servers](/docs/en/agent-sdk/mcp) can also expose [MCP prompts as commands](/docs/en/mcp#use-mcp-prompts-as-commands).
159
160### Dispatch commands by name
161
162Send a command by including it in your prompt string, the same way you send regular text. Dispatch doesn't depend on the `skills` option. Sending `/<name>` runs a user-invocable skill even when your `skills` list omits it. Commands that act on conversation history, such as `/compact`, need prior messages to work with.
163
164A `/<name>` that matches neither a command in the session nor a built-in Claude Code command doesn't fail the query. Claude Code sends the prompt to Claude as an ordinary message, with a note that the command didn't run, so the query spends a model turn and returns Claude's reply. Before v2.1.274, a `/<name>` that matched nothing returned `Unknown command: /<name>` as the result without a model turn.
165
166A `/<name>` that matches a built-in Claude Code command that isn't available in the session, such as `/theme`, returns `/theme isn't available in this environment.` as the result without a model turn.
167
168<Note>
169 A command can hit the `maxTurns` / `max_turns` limit like any other prompt, ending the query with an error result instead of `success`. For the error-result contract, see [Handle the result](/docs/en/agent-sdk/agent-loop#handle-the-result). If your command might hit the limit, wrap the loop in a `try`/`catch` in TypeScript or `try`/`except` in Python, as shown in [Single Message Input](/docs/en/agent-sdk/streaming-vs-single-mode#single-message-input), or set `maxTurns` high enough for the work to complete.
170</Note>
171
172### Compact history with `/compact`
173
174The `/compact` command reduces the size of your conversation history by summarizing older messages while preserving important context. Compaction needs an existing conversation with enough prior messages to summarize. This example has a conversation first, then compacts it and reads the `compact_boundary` system message that reports the result:
175
176<CodeGroup>
177 ```typescript TypeScript theme={null}
178 import { query } from "@anthropic-ai/claude-agent-sdk";
179
180 // Compaction needs existing history, so have a conversation first
181 try {
182 for await (const message of query({
183 prompt: "Explain what this project does",
184 options: { maxTurns: 2 }
185 })) {
186 if (message.type === "result" && message.subtype === "success") {
187 console.log(message.result);
188 }
189 }
190 } catch (error) {
191 // A single-shot query() throws after yielding an error result,
192 // so the follow-up query below still runs.
193 console.error(`Session ended with an error: ${error}`);
183} catch (error) {
184 // A single-shot query() throws after yielding an error result,
185 // so the follow-up query below still runs.
186 console.error(`Session ended with an error: ${error}`);
187}
188
189// Compact the same conversation
190for await (const message of query({
191 prompt: "/compact",
192 options: { continue: true, maxTurns: 1 }
193})) {
194 if (message.type === "system" && message.subtype === "compact_boundary") {
195 console.log("Compaction completed");
196 console.log("Pre-compaction tokens:", message.compact_metadata.pre_tokens);
197 console.log("Trigger:", message.compact_metadata.trigger);
198 // Example output:
199 // Compaction completed
200 // Pre-compaction tokens: 1842
201 // Trigger: manual
194202 }
195
196 // Compact the same conversation
197 for await (const message of query({
198 prompt: "/compact",
199 options: { continue: true, maxTurns: 1 }
200 })) {
201 if (message.type === "system" && message.subtype === "compact_boundary") {
202 console.log("Compaction completed");
203 console.log("Pre-compaction tokens:", message.compact_metadata.pre_tokens);
204 console.log("Trigger:", message.compact_metadata.trigger);
205 // Example output:
206 // Compaction completed
207 // Pre-compaction tokens: 1842
208 // Trigger: manual
209 }
210 }
211 ```
212
213 ```python Python theme={null}
214 import asyncio
215 from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage, SystemMessage
216
217
218 async def main():
219 # Compaction needs existing history, so have a conversation first
220 try:
221 async for message in query(
222 prompt="Explain what this project does",
223 options=ClaudeAgentOptions(max_turns=2),
224 ):
225 if isinstance(message, ResultMessage) and message.subtype == "success":
226 print(message.result)
227 except Exception as error:
228 # A single-shot query() raises after yielding an error result,
229 # so the follow-up query below still runs.
230 print(f"Session ended with an error: {error}")
231
232 # Compact the same conversation
233 async for message in query(
234 prompt="/compact",
235 options=ClaudeAgentOptions(continue_conversation=True, max_turns=1),
236 ):
237 if isinstance(message, SystemMessage) and message.subtype == "compact_boundary":
238 print("Compaction completed")
239 print("Pre-compaction tokens:", message.data["compact_metadata"]["pre_tokens"])
240 print("Trigger:", message.data["compact_metadata"]["trigger"])
241 # Example output:
242 # Compaction completed
243 # Pre-compaction tokens: 1842
244 # Trigger: manual
245
246
247 asyncio.run(main())
248 ```
249</CodeGroup>
250
251<Note>
252 A `compact_boundary` message only arrives when compaction ran. When a continued session has messages but nothing `/compact` can summarize, the run still ends with a `success` result rather than an error, and no `compact_boundary` message arrives. The result text then carries the reason, for example `Not enough messages to compact.` when the session holds a prompt but no reply from Claude yet. A fresh one-shot `query()` call starts with empty context, so use this pattern in a session with prior turns, for example in [streaming input mode](/docs/en/agent-sdk/streaming-vs-single-mode) or when resuming a session.
253</Note>
203}
204```
205
206```python Python theme={null}
207import asyncio
208from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage, SystemMessage
209
210
211async def main():
212 # Compaction needs existing history, so have a conversation first
213 try:
214 async for message in query(
215 prompt="Explain what this project does",
216 options=ClaudeAgentOptions(max_turns=2),
217 ):
218 if isinstance(message, ResultMessage) and message.subtype == "success":
219 print(message.result)
220 except Exception as error:
221 # A single-shot query() raises after yielding an error result,
222 # so the follow-up query below still runs.
223 print(f"Session ended with an error: {error}")
224
225 # Compact the same conversation
226 async for message in query(
227 prompt="/compact",
228 options=ClaudeAgentOptions(continue_conversation=True, max_turns=1),
229 ):
230 if isinstance(message, SystemMessage) and message.subtype == "compact_boundary":
231 print("Compaction completed")
232 print("Pre-compaction tokens:", message.data["compact_metadata"]["pre_tokens"])
233 print("Trigger:", message.data["compact_metadata"]["trigger"])
234 # Example output:
235 # Compaction completed
236 # Pre-compaction tokens: 1842
237 # Trigger: manual
238
239
240asyncio.run(main())
241```
242
243> [!NOTE]
244>
245> A `compact_boundary` message only arrives when compaction ran. When a continued session has messages but nothing `/compact` can summarize, the run still ends with a `success` result rather than an error, and no `compact_boundary` message arrives. The result text then carries the reason, for example `Not enough messages to compact.` when the session holds a prompt but no reply from Claude yet. A fresh one-shot `query()` call starts with empty context, so use this pattern in a session with prior turns, for example in [streaming input mode](/docs/en/agent-sdk/streaming-vs-single-mode) or when resuming a session.
254246
255247### Reset context with `/clear`
256248
from line 289
297289
298290Once the file exists, the skill is available through the SDK. Claude invokes it when a request matches its description, and you can dispatch it directly:
299291
300<CodeGroup>
301 ```typescript TypeScript theme={null}
302 import { query } from "@anthropic-ai/claude-agent-sdk";
303
304 for await (const message of query({
305 prompt: "/security-check",
306 options: { maxTurns: 10 }
307 })) {
308 if (message.type === "result" && message.subtype === "success") {
309 console.log(message.result);
310 }
292```typescript TypeScript theme={null}
293import { query } from "@anthropic-ai/claude-agent-sdk";
294
295for await (const message of query({
296 prompt: "/security-check",
297 options: { maxTurns: 10 }
298})) {
299 if (message.type === "result" && message.subtype === "success") {
300 console.log(message.result);
311301 }
312 ```
313
314 ```python Python theme={null}
315 import asyncio
316 from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
317
318
319 async def main():
320 async for message in query(
321 prompt="/security-check", options=ClaudeAgentOptions(max_turns=10)
322 ):
323 if isinstance(message, ResultMessage) and message.subtype == "success":
324 print(message.result)
325
326
327 asyncio.run(main())
328 ```
329</CodeGroup>
302}
303```
304
305```python Python theme={null}
306import asyncio
307from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
308
309
310async def main():
311 async for message in query(
312 prompt="/security-check", options=ClaudeAgentOptions(max_turns=10)
313 ):
314 if isinstance(message, ResultMessage) and message.subtype == "success":
315 print(message.result)
316
317
318asyncio.run(main())
319```
330320
331321A successful run ends with a `success` result whose text carries the scan findings. Against a small Express app with seeded issues, the result text begins:
332322
from line 329
339329
340330The skill's name also appears in the init message's `slash_commands` array.
341331
342<Note>
343 Claude Code includes bundled `code-review` and `verify` skills. If you name a `.claude/commands/` file after one of them, for example `.claude/commands/code-review.md`, the file's command shadows the bundled skill and `slash_commands` lists the name once.
344</Note>
332> [!NOTE]
333>
334> Claude Code includes bundled `code-review` and `verify` skills. If you name a `.claude/commands/` file after one of them, for example `.claude/commands/code-review.md`, the file's command shadows the bundled skill and `slash_commands` lists the name once.
345335
346336## Pre-approve tools for skills
347337
348<Note>
349 In SDK sessions, you can pre-approve tools for a project or personal skill with the skill's [`allowed-tools`](/docs/en/skills#pre-approve-tools-for-a-skill) frontmatter or with the `allowedTools` option (`allowed_tools` in Python) in your query configuration. If your organization sets [`allowManagedPermissionRulesOnly`](/docs/en/settings-reference#allowmanagedpermissionrulesonly) in managed settings, Claude Code ignores both. Skills [synced from claude.ai](/docs/en/skills#how-claude-code-handles-the-frontmatter-of-a-synced-skill) follow their own frontmatter rules.
350</Note>
338> [!NOTE]
339>
340> In SDK sessions, you can pre-approve tools for a project or personal skill with the skill's [`allowed-tools`](/docs/en/skills#pre-approve-tools-for-a-skill) frontmatter or with the `allowedTools` option (`allowed_tools` in Python) in your query configuration. If your organization sets [`allowManagedPermissionRulesOnly`](/docs/en/settings-reference#allowmanagedpermissionrulesonly) in managed settings, Claude Code ignores both. Skills [synced from claude.ai](/docs/en/skills#how-claude-code-handles-the-frontmatter-of-a-synced-skill) follow their own frontmatter rules.
351341
352342Skills run with the session's tools. The example below pre-approves `Read`, `Grep`, and `Glob` with `allowedTools` (`allowed_tools` in Python), so Claude can inspect files while running the [security-check skill](#create-and-dispatch-your-first-skill) without stopping for approval:
353343
354<CodeGroup>
355 ```python Python theme={null}
356 import asyncio
357
358 from claude_agent_sdk import query, ClaudeAgentOptions
359
360 options = ClaudeAgentOptions(
361 setting_sources=["user", "project"], # Load skills from filesystem
362 skills="all",
363 allowed_tools=["Read", "Grep", "Glob"],
364 )
365
366
367 async def main():
368 async for message in query(prompt="Check this project for security issues", options=options):
369 print(message)
370
371
372 asyncio.run(main())
373 ```
374
375 ```typescript TypeScript theme={null}
376 import { query } from "@anthropic-ai/claude-agent-sdk";
377
378 for await (const message of query({
379 prompt: "Check this project for security issues",
380 options: {
381 settingSources: ["user", "project"], // Load skills from filesystem
382 skills: "all",
383 allowedTools: ["Read", "Grep", "Glob"]
384 }
385 })) {
386 console.log(message);
344```python Python theme={null}
345import asyncio
346
347from claude_agent_sdk import query, ClaudeAgentOptions
348
349options = ClaudeAgentOptions(
350 setting_sources=["user", "project"], # Load skills from filesystem
351 skills="all",
352 allowed_tools=["Read", "Grep", "Glob"],
353)
354
355
356async def main():
357 async for message in query(prompt="Check this project for security issues", options=options):
358 print(message)
359
360
361asyncio.run(main())
362```
363
364```typescript TypeScript theme={null}
365import { query } from "@anthropic-ai/claude-agent-sdk";
366
367for await (const message of query({
368 prompt: "Check this project for security issues",
369 options: {
370 settingSources: ["user", "project"], // Load skills from filesystem
371 skills: "all",
372 allowedTools: ["Read", "Grep", "Glob"]
387373 }
388 ```
389</CodeGroup>
374})) {
375 console.log(message);
376}
377```
390378
391379In the stream, the skill invocation appears as a Skill tool use, followed by Read calls on the project files. The run ends with a `success` result whose text carries the findings.
392380
from line 386
398386
399387**Check settingSources configuration**: the SDK discovers skills through the `user` and `project` setting sources. If you set `settingSources`/`setting_sources` explicitly and omit those sources, the SDK doesn't load skills:
400388
401<CodeGroup>
402 ```python Python theme={null}
403 # Skills not loaded: setting_sources excludes user and project
404 options = ClaudeAgentOptions(setting_sources=[], skills="all")
405
406 # Skills loaded: user and project sources included
407 options = ClaudeAgentOptions(
408 setting_sources=["user", "project"],
409 skills="all",
410 )
411 ```
412
413 ```typescript TypeScript theme={null}
414 // Skills not loaded: settingSources excludes user and project
415 const optionsWithoutSkills = {
416 settingSources: [],
417 skills: "all"
418 };
419
420 // Skills loaded: user and project sources included
421 const optionsWithSkills = {
422 settingSources: ["user", "project"],
423 skills: "all"
424 };
425 ```
426</CodeGroup>
389```python Python theme={null}
390# Skills not loaded: setting_sources excludes user and project
391options = ClaudeAgentOptions(setting_sources=[], skills="all")
392
393# Skills loaded: user and project sources included
394options = ClaudeAgentOptions(
395 setting_sources=["user", "project"],
396 skills="all",
397)
398```
399
400```typescript TypeScript theme={null}
401// Skills not loaded: settingSources excludes user and project
402const optionsWithoutSkills = {
403 settingSources: [],
404 skills: "all"
405};
406
407// Skills loaded: user and project sources included
408const optionsWithSkills = {
409 settingSources: ["user", "project"],
410 skills: "all"
411};
412```
427413
428414For which skill directories each source loads, see the [filesystem sources table](/docs/en/agent-sdk/claude-code-features#control-filesystem-settings-with-settingsources). For more details on `settingSources`/`setting_sources`, see the [TypeScript SDK reference](/docs/en/agent-sdk/typescript#settingsource) or [Python SDK reference](/docs/en/agent-sdk/python#settingsource).
429415
430416**Check working directory**: the SDK loads skills from `.claude/skills/` in the `cwd` option and in every parent directory up to the repository root. Ensure `cwd` points at or below the directory containing `.claude/skills/`, within the same repository:
431417
432<CodeGroup>
433 ```python Python theme={null}
434 # Ensure your cwd points to the directory containing .claude/skills/
435 options = ClaudeAgentOptions(
436 cwd="/path/to/project", # .claude/skills/ here or in a parent directory
437 setting_sources=["user", "project"], # Loads skills from these sources
438 skills="all",
439 )
440 ```
441
442 ```typescript TypeScript theme={null}
443 // Ensure your cwd points to the directory containing .claude/skills/
444 const options = {
445 cwd: "/path/to/project", // .claude/skills/ here or in a parent directory
446 settingSources: ["user", "project"], // Loads skills from these sources
447 skills: "all"
448 };
449 ```
450</CodeGroup>
418```python Python theme={null}
419# Ensure your cwd points to the directory containing .claude/skills/
420options = ClaudeAgentOptions(
421 cwd="/path/to/project", # .claude/skills/ here or in a parent directory
422 setting_sources=["user", "project"], # Loads skills from these sources
423 skills="all",
424)
425```
426
427```typescript TypeScript theme={null}
428// Ensure your cwd points to the directory containing .claude/skills/
429const options = {
430 cwd: "/path/to/project", // .claude/skills/ here or in a parent directory
431 settingSources: ["user", "project"], // Loads skills from these sources
432 skills: "all"
433};
434```
451435
452436See [Use skills with the Agent SDK](#use-skills-with-the-agent-sdk) for the complete pattern.
453437
from line 462
478462
479463Each SDK surfaces the rejection differently:
480464
481<Tabs>
482 <Tab title="TypeScript">
483 The TypeScript SDK throws an `Error` stating the rule the entry broke. For example, `skills: ["docs:*"]` throws:
484
485 ```text theme={null}
486 Invalid skill name "docs:*": wildcard-suffix names are not allowed; list each skill by its exact name.
487 ```
488
489 An empty name reports `Skill names must be non-empty strings.`
490
491 Before TypeScript Agent SDK 0.3.221, the SDK didn't run this check.
492 </Tab>
493
494 <Tab title="Python">
495 The Python SDK raises `ValueError` stating the rule the entry broke. For example, `skills=["docs:*"]` raises:
496
497 ```text theme={null}
498 ValueError: Invalid skill name 'docs:*': wildcard-suffix names are not allowed; list each skill by its exact name.
499 ```
500
501 An empty name reports `Skill names must be non-empty strings`.
502
503 Before Python Agent SDK 0.2.129, the SDK didn't run this check.
504 </Tab>
505</Tabs>
465**TypeScript**
466
467The TypeScript SDK throws an `Error` stating the rule the entry broke. For example, `skills: ["docs:*"]` throws:
468
469```text theme={null}
470Invalid skill name "docs:*": wildcard-suffix names are not allowed; list each skill by its exact name.
471```
472
473An empty name reports `Skill names must be non-empty strings.`
474
475Before TypeScript Agent SDK 0.3.221, the SDK didn't run this check.
476
477**Python**
478
479The Python SDK raises `ValueError` stating the rule the entry broke. For example, `skills=["docs:*"]` raises:
480
481```text theme={null}
482ValueError: Invalid skill name 'docs:*': wildcard-suffix names are not allowed; list each skill by its exact name.
483```
484
485An empty name reports `Skill names must be non-empty strings`.
486
487Before Python Agent SDK 0.2.129, the SDK didn't run this check.
506488
507489### Additional troubleshooting
508490
509491
No line in this hunk matches that.