DEV Community

Cover image for How to Create Claude Code Skills That Actually Trigger (SKILL.md Guide)
Kuldeep Paul
Kuldeep Paul

Posted on

How to Create Claude Code Skills That Actually Trigger (SKILL.md Guide)

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 a Claude Code skill loads in three tiers: metadata, instructions, resources

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:

  1. The description alone decides whether the skill triggers. The body is invisible until then.
  2. 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.
  3. 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
Enter fullscreen mode Exit fullscreen mode

Step 3: Write valid frontmatter

The Agent Skills specification defines the portable fields:

  • name: 1 to 64 characters, lowercase a-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; put version here, quoted), and allowed-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"
---
Enter fullscreen mode Exit fullscreen mode

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 like disable_model_invocation silently does nothing.
  • The combined description and when_to_use text 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>.
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

Rules that hold up in testing:

  1. What, then when. One sentence of concrete capability, then the situations.
  2. Intent, not implementation. "Use when the user wants to..." beats "This skill uses a Python script to...".
  3. 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".
  4. Name the near-misses when a sibling skill overlaps: "Not for Excel formula edits; use spreadsheet-editing."
  5. 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)
Enter fullscreen mode Exit fullscreen mode

Guidelines:

  • Target 150 to 400 lines in SKILL.md. Above 500, split.
  • Move detail into references/<topic>.md and say when to read each file: "Read references/api-errors.md if 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, --help output, 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.

Trigger eval loop for a skill description: write queries, split, run, score, edit, ship

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}
]
Enter fullscreen mode Exit fullscreen mode

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: .}'
Enter fullscreen mode Exit fullscreen mode

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.

  1. The description describes a tool, not a situation. "Database helper" will not fire. Rewrite with the formula above.
  2. The frontmatter is not being read. The opening --- must be the first line, field names must match exactly (hyphens included), and name must equal the folder name and use only lowercase letters, digits, and single hyphens.
  3. The skill is in the wrong directory for the agent you are running. Claude Code reads .claude/skills/, Codex reads .agents/skills/.
  4. A same-named skill shadows it. A personal skill in ~/.claude/skills/ beats the project copy.
  5. disable-model-invocation: true is set. That makes the skill manual-only (/skill-name).
  6. 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.
  7. 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
Enter fullscreen mode Exit fullscreen mode

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.version plus a CHANGELOG.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, broad rm -rf, reads of ~/.ssh or ~/.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
Enter fullscreen mode Exit fullscreen mode

The same name with different hashes is drift.

Install the agent-skills-for-teams skill

agent-skills-for-teams: write, version, review and ship Agent Skills across a team

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.

npx skills add build-with-ai-hq/agent-skills-for-teams
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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:

  1. 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).
  2. Rewrites descriptions so they trigger on real user phrasing and sets up a trigger-rate eval with train and validation splits.
  3. Restructures bloated skills with progressive disclosure.
  4. Adds SemVer, changelogs, a CI lint, and a version-bump check, plus a review and security checklist.
  5. 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
Enter fullscreen mode Exit fullscreen mode

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

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)