To create a Claude Code skill, make a folder named after the skill inside .claude/skills/ (for one repository) or ~/.claude/skills/ (for every project on your machine), put a SKILL.md file in it with YAML frontmatter and Markdown instructions, and write a description that says what the skill does and when to use it. Claude reads only that description when deciding whether to load the skill, so most "my skill never triggers" problems are description problems.
This guide covers deciding whether something should be a skill, the file layout, the frontmatter rules, a description formula that triggers on real requests, keeping the body small, and testing triggering the way you would test code. Everything here also applies to other coding agents that read the Agent Skills format, such as Codex, Cursor, Gemini CLI, and OpenCode.
How Claude Code loads a skill
A skill is a directory with a SKILL.md file (YAML frontmatter plus a Markdown body) and optional scripts/, references/, and assets/ folders. Coding agents load it in three tiers:
| Tier | What loads | When | Budget |
|---|---|---|---|
| 1. Metadata |
name and description of every installed skill |
Session start, always | About 100 tokens per skill |
| 2. Instructions | The full SKILL.md body |
When the description matches the task | Under 5,000 tokens and 500 lines |
| 3. Resources | Files in references/, scripts/, assets/
|
Only when the body tells the agent to open them | As needed |
Three rules follow from this table, and the rest of the guide builds on them:
- The description alone decides whether the skill triggers. The body is invisible until then.
- Every installed skill costs tier-1 tokens in every session. Ten unused skills cost roughly 1,000 tokens of metadata per session and compete with the skills you do want.
- Everything in the body is paid for on every activation, so detail that only some tasks need belongs in
references/.
Step 1: Decide whether it should be a skill
A skill earns its place when the agent would get a task wrong, slow, or inconsistent without it. Good candidates:
- Project conventions the model cannot infer: this repo's migration tool, its test layout, its release steps.
- Internal APIs, schemas, runbooks, and incident procedures.
- Fragile sequences that must run exactly, such as a deploy or a data backfill.
- Corrections you keep typing into chats. Every correction you have made to an agent is a candidate gotcha.
Poor candidates are general knowledge ("how to write good Python"), one-off tasks, and anything a linter or formatter already enforces. Those belong in a linter config, a repo CLAUDE.md or AGENTS.md, or nowhere.
Scope a skill like a function: one coherent unit of work. Too narrow and several skills load for one task with conflicting instructions. Too broad and the description cannot trigger precisely.
Build skills from real material, not from a prompt that says "write a skill about X." The steps that worked on a real task, review comments, runbooks, and postmortem fixes produce instructions the model does not already know. A generated skill about a general topic mostly restates what the model knows already.
Step 2: Create the folder in the right place
Claude Code reads skills from these locations, according to the Claude Code skills documentation:
| Location | Path | Loads in |
|---|---|---|
| Personal | ~/.claude/skills/<skill-name>/SKILL.md |
All your projects on this machine |
| Project | .claude/skills/<skill-name>/SKILL.md |
Sessions in this repository; commit it so your team gets it |
| Nested | <subdir>/.claude/skills/<skill-name>/SKILL.md |
Sessions started in or below that subdirectory |
| Plugin | <plugin>/skills/<skill-name>/SKILL.md |
Wherever the plugin is enabled, as /plugin-name:skill-name
|
| Enterprise | Managed settings directory | All users on machines where your organization deploys it |
When names collide, enterprise wins over personal, and personal wins over project. Do not keep a team skill and a personal skill with the same name: you will forget which one is running.
Other coding agents use different directories. The skills CLI agent table lists them:
| Agent | Project path | Global path |
|---|---|---|
| Claude Code | .claude/skills/ |
~/.claude/skills/ |
| Codex | .agents/skills/ |
~/.agents/skills/ |
| Cursor | .agents/skills/ |
~/.cursor/skills/ |
| Gemini CLI | .agents/skills/ |
~/.gemini/skills/ |
| GitHub Copilot | .agents/skills/ |
~/.copilot/skills/ |
| OpenCode | .agents/skills/ |
~/.config/opencode/skills/ |
A minimal layout looks like this:
.claude/skills/db-migrations/
├── SKILL.md
├── references/
│ └── online-index.md
└── scripts/
└── migrate.py
Step 3: Write valid frontmatter
The Agent Skills specification defines the portable fields:
-
name: 1 to 64 characters, lowercasea-z,0-9, and single hyphens. It must equal the folder name. -
description: 1 to 1,024 characters. Required by the spec. - Optional:
license,compatibility(500 characters or fewer, only for real environment requirements),metadata(a string-to-string map; putversionhere, quoted), andallowed-tools(experimental, support varies by agent).
---
name: db-migrations
description: Write, review and safely apply Postgres schema migrations with this repo's
migrate tool, including backfills, lock-safe index creation and rollback scripts. Use
when the user adds or changes a column, table, index or constraint, asks why a deploy
is blocked on a migration, or mentions "migration", "ALTER TABLE", "backfill" or
"schema change", even if they only describe the data change they want.
license: MIT
metadata:
version: "1.2.0"
---
Claude Code accepts extra fields on top of the spec, including when_to_use, disable-model-invocation, user-invocable, allowed-tools, context: fork, and hooks. Two details from the Claude Code docs catch people:
- Claude Code ignores a field it does not recognize without reporting an error, and it only reads frontmatter when the opening
---is the first line of the file. A typo likedisable_model_invocationsilently does nothing. - The combined
descriptionandwhen_to_usetext is truncated at 1,536 characters in the skill listing, so put the key use case first.
If your team uses several coding agents, keep shared skills to the spec fields. Harness-specific behavior (Claude Code's context: fork or hooks) is not portable, so put it in a separate, clearly named skill.
Step 4: Write a description that triggers
The description is the only thing the agent sees before deciding. Use this formula:
<What it does, with concrete verbs and nouns>. Use when <situations, including ones
where the user never names the domain>. Trigger on <phrases users actually type>.
<Optional: what it is NOT for, if a sibling skill overlaps>.
Compare a description that never fires with one that does:
# Weak: names a tool, says nothing about when
description: Internal release helper.
# Strong: what, when, real phrases, and a boundary
description: Cut, tag and publish a release of this monorepo's packages with the
release script, including changelog generation, version bumps and npm publish
with provenance. Use when the user wants to release, ship, publish or bump a
package version, asks why a release CI job failed, or says "cut a release" or
"tag v1.x". Not for deploying the web app; use deploy-web.
Rules that hold up in testing:
- What, then when. One sentence of concrete capability, then the situations.
- Intent, not implementation. "Use when the user wants to..." beats "This skill uses a Python script to...".
- Cover indirect phrasings. List symptoms and goals as well as domain keywords: "deploy is stuck", "column is missing in prod", "even if they don't say migration".
- Name the near-misses when a sibling skill overlaps: "Not for Excel formula edits; use spreadsheet-editing."
- No marketing adjectives. "Powerful" and "comprehensive" trigger nothing.
Agents also tend to skip skills for tasks they believe are simple. A perfect description will not trigger on "read this file." Triggering matters most where the skill carries knowledge the model lacks.
Step 5: Keep the body small with progressive disclosure
A structure that works for most skills:
# <Title>
One line: what the agent achieves.
## When to use (short; the description already did the heavy lifting)
## Steps (numbered; exact commands for fragile parts)
## Defaults (the one tool or approach to use, with an escape hatch)
## Gotchas (environment facts that defy reasonable assumptions)
## Verify (how the agent checks its own work)
## References (each file, and when to read it)
Guidelines:
- Target 150 to 400 lines in
SKILL.md. Above 500, split. - Move detail into
references/<topic>.mdand say when to read each file: "Readreferences/api-errors.mdif the API returns a non-200 status." A bare "see references/" means the agent opens none of them or all of them. - Keep references one level deep. No reference-to-reference chains.
- Move logic the agent re-derives every run into a tested script in
scripts/, with dependencies stated,--helpoutput, non-zero exit codes, and no interactive prompts. - Prefer one default with an escape hatch over a menu of equal options. Menus make agents try several approaches.
- Cut every line where the answer to "would the agent get this wrong without it?" is no.
The Claude Code cost guide makes the same point from the billing side: CLAUDE.md loads at session start, while skills load on demand, so moving specialized workflow instructions out of CLAUDE.md and into skills keeps every session's base context smaller.
Step 6: Test triggering like code
Guessing whether a description works is how skills end up never loading. Build an eval set instead.
Write about 20 realistic queries in a JSON file. Half should trigger, including indirect phrasings that never name the domain. Half should be near-misses that share keywords but need something else. "What's the weather" tests nothing.
[
{"query": "prod deploy is stuck on 'waiting for migration lock' again, can you look", "should_trigger": true},
{"query": "add a nullable archived_at column to invoices and backfill it from status", "should_trigger": true},
{"query": "write a SQL query for last month's revenue by region", "should_trigger": false},
{"query": "our ORM model for invoices is missing a type hint, fix it", "should_trigger": false}
]
Split it roughly 60/40 into train.json and validation.json, run each query three times, and count a pass when should-trigger queries load the skill at least half the time and near-misses load it less than half the time. This runner is adapted from the agentskills.io guide to optimizing descriptions; check the JSON shape your Claude Code version emits before relying on it:
#!/usr/bin/env bash
set -euo pipefail
QUERIES="${1:?usage: $0 queries.json}"; SKILL="${2:?skill name}"; RUNS="${RUNS:-3}"
check_triggered() {
claude -p "$1" --output-format json 2>/dev/null |
jq -e --arg s "$SKILL" 'any(.messages[].content[]?; .type=="tool_use" and .name=="Skill" and .input.skill==$s)' >/dev/null
}
n=$(jq length "$QUERIES")
for i in $(seq 0 $((n-1))); do
q=$(jq -r ".[$i].query" "$QUERIES"); want=$(jq -r ".[$i].should_trigger" "$QUERIES"); hits=0
for _ in $(seq 1 "$RUNS"); do check_triggered "$q" && hits=$((hits+1)) || true; done
jq -n --arg q "$q" --argjson want "$want" --argjson h "$hits" --argjson r "$RUNS" \
'{query:$q, should_trigger:$want, rate:($h/$r), pass:(if $want then ($h/$r)>=0.5 else ($h/$r)<0.5 end)}'
done | jq -s '{pass_rate: ((map(select(.pass))|length)/length), results: .}'
Iterate on the train set only, usually for five rounds or fewer:
- Positives failing: the description is too narrow. Add the category of situation, not the literal failing words.
- Negatives triggering: it is too broad. Add a "Not for..." boundary.
- Stuck: rewrite the description's structure instead of appending more phrases.
- Recheck the 1,024-character limit every round.
Then run an output eval. For three to five real tasks, run the agent with and without the skill and compare correctness and wasted steps. If the agent does equally well without it, the skill adds nothing; delete it.
Why your Claude skill is not triggering
Work through this list in order. Most cases stop at the first two items.
- The description describes a tool, not a situation. "Database helper" will not fire. Rewrite with the formula above.
-
The frontmatter is not being read. The opening
---must be the first line, field names must match exactly (hyphens included), andnamemust equal the folder name and use only lowercase letters, digits, and single hyphens. -
The skill is in the wrong directory for the agent you are running. Claude Code reads
.claude/skills/, Codex reads.agents/skills/. -
A same-named skill shadows it. A personal skill in
~/.claude/skills/beats the project copy. -
disable-model-invocation: trueis set. That makes the skill manual-only (/skill-name). - The task looks too simple to the model. Agents skip skills for tasks they think they can do alone. Name the indirect symptoms in the description.
- The session started before you edited the skill. Running sessions keep the already-loaded version. Start a fresh session.
To check discovery, ask the agent in a fresh session to list its available skills, or run claude --debug and look for the skill's name in the debug log.
How to add skills to Claude Code from GitHub
You do not need to hand-copy folders into each agent's directory. The open-source skills CLI installs a skill from a GitHub repository and symlinks one canonical copy into every agent you select:
npx skills add your-org/team-skills --list # see what the repo offers
npx skills add your-org/team-skills --skill db-migrations -a claude-code codex cursor -y
npx skills add your-org/team-skills --skill db-migrations -g -a claude-code -y # user-wide
npx skills list # what is installed, and from where
npx skills update db-migrations # upgrade deliberately
skills.sh is the public directory of skills you can install this way. Private repositories work with your existing git, gh, or SSH authentication.
Share, version, and review skills across a team
Once more than one person depends on a skill, it behaves like an API whose consumer is the agent. A one-line edit changes what every engineer's agent does in the next session, with no build and no visible diff in the code they are working on. Four habits keep that under control:
-
One source of truth. Every skill has exactly one canonical home. Repo-specific skills live in the repo; cross-cutting skills live in a shared skills repo installed with
npx skills add. Delete copies once the shared source exists: a skill copied into five repos is five skills. -
SemVer in
metadata.versionplus aCHANGELOG.md. Wording fixes are a patch. A new reference file or broader triggering is a minor. A renamed skill, a changed default command, or narrower triggering is a major. A description change is never "just docs," because it changes triggering. -
A review gate. Put CODEOWNERS on the skills directory and check scripts for
curl | sh,sudo, broadrm -rf, reads of~/.sshor~/.aws, and undocumented network calls. Read third-party skills in full before installing them. - A rehearsed rollback. Release from tags, pin consumers to a tag, and practice rolling back once before you need to.
To find drift between copies today, run this from the root of each repo and from ~:
find . \( -name node_modules -o -name .git \) -prune -o -name SKILL.md -print0 |
while IFS= read -r -d '' f; do
printf '%s\t%s\t%s\n' "$(sed -n 's/^name:[[:space:]]*//p' "$f" | head -1)" \
"$(shasum -a 256 "$f" | cut -c1-12)" "$f"
done | sort
The same name with different hashes is drift.
Install the agent-skills-for-teams skill
Everything in this guide is packaged as an open-source agent skill, so your coding agent can do the audit and the fixes for you.
- GitHub: github.com/build-with-ai-hq/agent-skills-for-teams
- skills.sh: skills.sh/build-with-ai-hq/agent-skills-for-teams
npx skills add build-with-ai-hq/agent-skills-for-teams
To install it into specific agents, or user-wide for Claude Code:
npx skills add build-with-ai-hq/agent-skills-for-teams -a claude-code codex cursor -y
npx skills add build-with-ai-hq/agent-skills-for-teams -g -a claude-code -y
Then ask your agent something like "my deploy skill never triggers, fix the description" or "audit the skills in this repo." When the skill loads, the agent:
- Audits the repo for every
SKILL.md, copy-paste drift, oversized skills, missing versions, and unreviewed scripts, using a bundled linter (scripts/lint_skills.py, standard library only). - Rewrites descriptions so they trigger on real user phrasing and sets up a trigger-rate eval with train and validation splits.
- Restructures bloated skills with progressive disclosure.
- Adds SemVer, changelogs, a CI lint, and a version-bump check, plus a review and security checklist.
- Picks a distribution model (in-repo, shared git repo with
npx skills add, a plugin marketplace, or a central registry) and installs consistently across coding agents.
The linter also works on its own:
python3 scripts/lint_skills.py . # exits 1 on errors; wire it into CI
python3 scripts/lint_skills.py ~/.claude/skills ./skills
For teams that need a central registry with immutable versions and one-click rollback, the skill also covers the Skills Repository built into Bifrost, the open-source AI gateway, which serves skills to Claude Code and Codex as a plugin marketplace (docs).
Related reading
- Skills vs MCP: When to Use a Claude Skill, an MCP Server, or Both
- How to Track Claude Code Usage Across Your Team (Per Developer, Per Project)
FAQ
What is the difference between a Claude Code skill and a slash command?
Claude Code merged custom commands into skills. A file at .claude/commands/deploy.md and a skill at .claude/skills/deploy/SKILL.md both create /deploy. Skills add a folder for supporting files, frontmatter that controls who can invoke them, and automatic loading when the description matches the task.
How long should a SKILL.md be?
Aim for 150 to 400 lines in the body and keep it under 500 lines (about 5,000 tokens). Move anything only some tasks need into references/ with an explicit "read this when..." instruction.
How do I stop Claude from loading a skill automatically?
Set disable-model-invocation: true in the frontmatter. The skill then runs only when you type /skill-name, and its description is not kept in context.
Can one skill work in Claude Code, Codex, and Cursor?
Yes, if it sticks to the spec fields (name, description, license, compatibility, metadata, allowed-tools). Install it with npx skills add ... -a claude-code codex cursor so each agent gets a link to the same canonical copy.
How do I know which version of a skill my teammates are running?
Put metadata.version in every skill and ask people (or a startup script) to report npx skills list. With a central registry, the served version is the answer for everyone who installs from it.



Top comments (0)