qwen-code/docs/users/features/skills.md
Shaojin Wen 09248c0745 fix(skills): comprehensive review pass — security, correctness, robustness
Eleven findings from /qreview (claude-opus-4-7), grouped by area:

CORRECTNESS

- C1: appendAdditionalContext silently dropped reminders for any tool
  whose llmContent is a single non-array Part (read-file returning
  inlineData for images / PDFs is the canonical case). Both the
  ConditionalRulesRegistry rule reminder and the path-conditional
  skill activation reminder were lost. Wrap the single-Part case
  into an array so the addition still lands.
- S2: Legacy tool-name aliases (`replace` → `edit`,
  `search_file_content` → `grep_search`, `task` → `agent`) bypassed
  FS_PATH_TOOL_NAMES. The registry resolves the alias at execute time
  but `request.name` keeps the alias, so `replace({ file_path: ... })`
  produced empty candidates and missed activation. Canonicalize via
  `ToolNamesMigration` before the allowlist check.
- S5: `new SkillActivationRegistry(...)` ran picomatch unguarded —
  pathological patterns (oversize / broken extglob) could throw and
  abort all of `refreshCache`. Wrap each picomatch call in try/catch
  inside the constructor; drop the bad pattern, keep the rest of
  the skill, log via debugLogger.
- S7: Extension parser (skill-load.ts) silently dropped
  `disable-model-invocation` and `when_to_use`. Now that we have
  `paths:`, that meant an extension SKILL.md with both `paths:` and
  `disable-model-invocation: true` would still fire path-activation
  reminders for a skill the model can't invoke — directly
  contradicting the bug_004 fix at the project/user level.
- S8: SkillTool discarded the `addChangeListener` cleanup function
  and had no `dispose()`. Subagents share the parent's SkillManager
  via `InProcessBackend.createPerAgentConfig`, so each per-subagent
  SkillTool registered another listener; with the listener pipeline
  now async, every path activation serialized through every stale
  subagent's refresh chain. Mirror AgentTool: store the cleanup,
  expose `dispose()`.

SECURITY / SUPPLY-CHAIN

- S11: `validateSkillName`'s `/^[a-zA-Z0-9_:.-]+$/` rejected every
  non-ASCII name on upgrade, silently dropping CJK / Cyrillic /
  accented Latin skills. The structural-injection guard targets
  `<>"'/\n\r\t` etc; entire Unicode planes are not the threat.
  Widen to `/^[\p{L}\p{N}_:.-]+$/u`. Update docs/users/features/
  skills.md to match.
- S10: `parsePathsField` only validated shape (must-be-array). Now
  also reject leading-slash absolute patterns and `..` parent-escape
  patterns at parse time — these silently never match anything in
  the activation registry, so an author who writes `paths:
  ['/etc/passwd']` or `['../*.ts']` would otherwise see the skill in
  /skills and never understand why it never activates.

ROBUSTNESS

- S3: `coreToolScheduler` emitted "skill X is now available via the
  Skill tool" even when the calling subagent's tool registry did not
  expose SkillTool (subagent's `tools:` allowlist excluded `skill`).
  Gate the reminder on `toolRegistry.getTool(ToolNames.SKILL)`.
- S4: `extensionManager.refreshMemory` used `Promise.all` so a
  rejection from skill or subagent refresh nuked the other leg AND
  the hierarchical-memory refresh below it. Switch to
  `Promise.allSettled`, log each rejection, and `await` the
  hierarchical refresh too (the comment justifies awaiting; the
  code didn't).
- S9 / S12: `docs/users/features/skills.md` claimed `paths:` only
  gates model discovery and slash invocation always works. True for
  the user-side path itself, but if the model then tries to chain
  off the user's invocation (call `Skill { skill: ... }` itself),
  validateToolParams returns "gated by path-based activation" —
  contradicting the doc. Rephrase to call out the model-side
  limitation explicitly.

DEFERRED

- S6: notifyChangeListeners swallows per-listener errors and the
  reminder still fires. Real concern but the fix needs an API
  shape change (listener-failure signal back to the scheduler);
  worth its own design discussion. Logged here for follow-up.

Adds 12 regression tests across the 7 affected files. 632 tests
pass; types and lint clean.
2026-05-01 17:21:37 +08:00

8.9 KiB

Agent Skills

Create, manage, and share Skills to extend Qwen Code's capabilities.

This guide shows you how to create, use, and manage Agent Skills in Qwen Code. Skills are modular capabilities that extend the model's effectiveness through organized folders containing instructions (and optionally scripts/resources).

Prerequisites

  • Qwen Code (recent version)
  • Basic familiarity with Qwen Code (Quickstart)

What are Agent Skills?

Agent Skills package expertise into discoverable capabilities. Each Skill consists of a SKILL.md file with instructions that the model can load when relevant, plus optional supporting files like scripts and templates.

How Skills are invoked

Skills are model-invoked — the model autonomously decides when to use them based on your request and the Skill's description. This is different from slash commands, which are user-invoked (you explicitly type /command).

If you want to invoke a Skill explicitly, use the /skills slash command:

/skills <skill-name>

Use autocomplete to browse available Skills and descriptions.

Benefits

  • Extend Qwen Code for your workflows
  • Share expertise across your team via git
  • Reduce repetitive prompting
  • Compose multiple Skills for complex tasks

Create a Skill

Skills are stored as directories containing a SKILL.md file.

Personal Skills

Personal Skills are available across all your projects. Store them in ~/.qwen/skills/:

mkdir -p ~/.qwen/skills/my-skill-name

Use personal Skills for:

  • Your individual workflows and preferences
  • Skills you're developing
  • Personal productivity helpers

Project Skills

Project Skills are shared with your team. Store them in .qwen/skills/ within your project:

mkdir -p .qwen/skills/my-skill-name

Use project Skills for:

  • Team workflows and conventions
  • Project-specific expertise
  • Shared utilities and scripts

Project Skills can be checked into git and automatically become available to teammates.

Write SKILL.md

Create a SKILL.md file with YAML frontmatter and Markdown content:

---
name: your-skill-name
description: Brief description of what this Skill does and when to use it
---

# Your Skill Name

## Instructions
Provide clear, step-by-step guidance for Qwen Code.

## Examples
Show concrete examples of using this Skill.

Field requirements

Qwen Code currently validates that:

  • name is a non-empty string matching /^[\p{L}\p{N}_:.-]+$/u — Unicode letters and digits (CJK / Cyrillic / accented Latin all OK), plus _, :, ., -. Whitespace, slashes, brackets and other structurally unsafe characters are rejected at parse time.
  • description is a non-empty string

Recommended conventions:

  • Prefer lowercase ASCII with hyphens for shareable names (e.g. tsx-helper)
  • Make description specific: include both what the Skill does and when to use it (key words users will naturally mention)

Optional: gate a Skill on file paths (paths:)

For Skills that only matter to specific parts of a codebase, add a paths: list of glob patterns. The Skill stays out of the model's available-skills listing until a tool call touches a matching file:

---
name: tsx-helper
description: React TSX component helper
paths:
  - 'src/**/*.tsx'
  - 'packages/*/src/**/*.tsx'
---

Notes:

  • Globs are matched relative to the project root with picomatch; files outside the project root never trigger activation.
  • A path-gated Skill stays activated for the rest of the session once a matching file is touched. A new session, or a refreshCache triggered by editing any Skill file, resets activations.
  • paths: only gates model discovery, and only at the SkillTool listing level. You can always invoke a path-gated Skill yourself via /<skill-name> or the /skills picker — that user path runs the Skill body regardless of activation state. The model side, however, stays gated until a matching file is touched: a slash invocation does not unlock model-side activation, so if you want the model to chain off your invocation (call Skill { skill: ... } itself), also access a file matching the skill's paths: first.
  • Combining paths: with disable-model-invocation: true is allowed but the gate has no effect — the Skill is hidden from the model regardless, so path activation never advertises it.

Add supporting files

Create additional files alongside SKILL.md:

my-skill/
├── SKILL.md (required)
├── reference.md (optional documentation)
├── examples.md (optional examples)
├── scripts/
│   └── helper.py (optional utility)
└── templates/
    └── template.txt (optional template)

Reference these files from SKILL.md:

For advanced usage, see [reference.md](reference.md).

Run the helper script:

```bash
python scripts/helper.py input.txt
```

View available Skills

Qwen Code discovers Skills from:

  • Personal Skills: ~/.qwen/skills/
  • Project Skills: .qwen/skills/
  • Extension Skills: Skills provided by installed extensions

Extension Skills

Extensions can provide custom skills that become available when the extension is enabled. These skills are stored in the extension's skills/ directory and follow the same format as personal and project skills.

Extension skills are automatically discovered and loaded when the extension is installed and enabled.

To see which extensions provide skills, check the extension's qwen-extension.json file for a skills field.

To view available Skills, ask Qwen Code directly:

What Skills are available?

Heads up — model vs. user view. Asking the model only surfaces Skills the model can currently see. If a Skill uses paths: (see "Optional: gate a Skill on file paths" above), it stays out of that listing until a matching file has been touched. The full set is always visible to you via the /skills slash command and on disk.

Or browse the full list with the slash command (always shows every Skill, including path-gated ones that have not activated yet):

/skills

Or inspect the filesystem:

# List personal Skills
ls ~/.qwen/skills/

# List project Skills (if in a project directory)
ls .qwen/skills/

# View a specific Skill's content
cat ~/.qwen/skills/my-skill/SKILL.md

Test a Skill

After creating a Skill, test it by asking questions that match your description.

Example: if your description mentions "PDF files":

Can you help me extract text from this PDF?

The model autonomously decides to use your Skill if it matches the request — you don't need to explicitly invoke it.

Debug a Skill

If Qwen Code doesn't use your Skill, check these common issues:

Make the description specific

Too vague:

description: Helps with documents

Specific:

description: Extract text and tables from PDF files, fill forms, merge documents. Use when working with PDFs, forms, or document extraction.

Verify file path

  • Personal Skills: ~/.qwen/skills/<skill-name>/SKILL.md
  • Project Skills: .qwen/skills/<skill-name>/SKILL.md
# Personal
ls ~/.qwen/skills/my-skill/SKILL.md

# Project
ls .qwen/skills/my-skill/SKILL.md

Check YAML syntax

Invalid YAML prevents the Skill metadata from loading correctly.

cat SKILL.md | head -n 15

Ensure:

  • Opening --- on line 1
  • Closing --- before Markdown content
  • Valid YAML syntax (no tabs, correct indentation)

View errors

Run Qwen Code with debug mode to see Skill loading errors:

qwen --debug

Share Skills with your team

You can share Skills through project repositories:

  1. Add the Skill under .qwen/skills/
  2. Commit and push
  3. Teammates pull the changes
git add .qwen/skills/
git commit -m "Add team Skill for PDF processing"
git push

Update a Skill

Edit SKILL.md directly:

# Personal Skill
code ~/.qwen/skills/my-skill/SKILL.md

# Project Skill
code .qwen/skills/my-skill/SKILL.md

Changes take effect the next time you start Qwen Code. If Qwen Code is already running, restart it to load the updates.

Remove a Skill

Delete the Skill directory:

# Personal
rm -rf ~/.qwen/skills/my-skill

# Project
rm -rf .qwen/skills/my-skill
git commit -m "Remove unused Skill"

Best practices

Keep Skills focused

One Skill should address one capability:

  • Focused: "PDF form filling", "Excel analysis", "Git commit messages"
  • Too broad: "Document processing" (split into smaller Skills)

Write clear descriptions

Help the model discover when to use Skills by including specific triggers:

description: Analyze Excel spreadsheets, create pivot tables, and generate charts. Use when working with Excel files, spreadsheets, or .xlsx data.

Test with your team

  • Does the Skill activate when expected?
  • Are the instructions clear?
  • Are there missing examples or edge cases?