Nightshift Dev
Agent Ops

Claude Code skills: write one that actually triggers

By Albert Nahas, 8 min read, updated

A Claude Code skill is a folder containing a SKILL.md file: YAML frontmatter with a description, then Markdown instructions. At startup Claude only sees each skill's description, and it loads the full file when your request matches that description or when you type /skill-name.

So the description decides whether a skill ever runs. I have dozens of personal skills in ~/.claude/skills and more in my project repos, and the ones that work are the ones whose descriptions read like the sentences I actually type. This guide covers where skills live, how they load, and how to write one that triggers.

The smallest useful skill

Create a folder and a file:

mkdir -p .claude/skills/summarize-changes
---
name: summarize-changes
description: Summarizes uncommitted changes and flags anything risky. Use when the user asks what changed, wants a commit message, or says "review my diff".
---

## Current changes

!`git diff HEAD`

## Instructions

Summarize the changes above in two or three bullets, then list any risks:
missing tests, changed public APIs, secrets, migrations.

That's a complete skill. Type /summarize-changes to run it, or ask "what did I change?" and Claude should pick it up on its own. Edits to SKILL.md under ~/.claude/skills/ or the project's .claude/skills/ are picked up without restarting. If you create a brand new top-level skills directory mid-session, run /reload-skills once.

The frontmatter has to start on the file's first line, between --- markers. Every field is optional, but without description Claude falls back to the first line of the body, which is rarely a good trigger.

Where skills live

LocationPathApplies to
Personal~/.claude/skills/<name>/SKILL.mdAll your projects on this machine
Project.claude/skills/<name>/SKILL.mdThis repo; commit it to share
Nested<subdir>/.claude/skills/<name>/SKILL.mdSessions started in or below that folder
Plugin<plugin>/skills/<name>/SKILL.mdWherever the plugin is enabled, as /plugin-name:skill-name
EnterpriseManaged settings directoryEveryone your org deploys it to

When names collide, enterprise beats personal and personal beats project. A skill also beats a same-named file in .claude/commands/. Old command files keep working and create the same /name entry, but skills add a folder for supporting files, invocation control and automatic loading, so I write new ones as skills.

My rule for placement: if the skill encodes how a repo works (its CLI, its deploy steps, its naming rules), it goes in the repo. If it encodes how I work (my writing voice, my research habits), it goes in ~/.claude/skills.

How skills load

This is the part worth understanding before you write a long one.

  1. At startup, only descriptions go into context. That's why you can have dozens installed without paying much for them.
  2. On invocation, the full rendered SKILL.md enters the conversation as one message and stays there for the rest of the session. Claude Code doesn't re-read the file later, so write instructions that still make sense ten turns on.
  3. On compaction, the most recent invocation of each skill is re-attached, keeping the first 5,000 tokens per skill within a combined 25,000-token budget. Anything past 5,000 tokens can fall off in a long session.
  4. Supporting files in the skill's folder are read only when SKILL.md points Claude to them and the task needs them. Scripts are executed, not loaded.

The docs say to keep SKILL.md under 500 lines and move reference material into separate files. Two of my own video pipeline skills break that: the image prompt skill is 571 lines and the pipeline CLI skill is 607. They work, but they're expensive on every call, and the CLI one still points at a home directory from a laptop I no longer use. A 600-line skill is hard to keep true. Splitting it into a short SKILL.md plus reference.md would have caught that path long ago, because I'd have read the short file.

pipeline-cli/
├── SKILL.md          # when to use, the 5 commands that matter, links below
├── reference.md      # every flag, loaded only when needed
├── examples/         # worked runs
└── scripts/
    └── status.sh     # run via ${CLAUDE_SKILL_DIR}/scripts/status.sh

Write a description that triggers

The combined description and when_to_use text is cut at 1,536 characters in the listing Claude sees, so put the main use case first. What works for me:

  • Say what it does, then when to use it. "Find local events and email them as a digest. Use when the user says..." beats a paragraph about the skill's philosophy.
  • Quote the phrases people type. My events skill lists "anything to do this weekend" and the slash command itself. Claude matches on wording, so give it the wording.
  • Name the output and the boundary. My image prompt skill says it outputs image_prompts.json and does not generate images. That sentence is there so it stays out of the way when I want actual images.
  • One job per skill. A description covering three jobs triggers weakly for all of them.

Even with good descriptions, skills didn't trigger as often as I wanted, so I added a UserPromptSubmit hook that reminds Claude to check its skill list before answering. It helps, but it's a patch. If a skill isn't triggering, fix the description first, then run /skill-doctor to see which skills cost context without ever being used.

Frontmatter fields worth knowing

FieldWhat it does
nameThe / command name; defaults to the folder name
descriptionWhat it does and when to use it; drives automatic loading
when_to_useExtra trigger context, appended to the description
argument-hintAutocomplete hint such as [issue-number]
disable-model-invocationtrue means only you can run it, never Claude on its own
user-invocablefalse hides it from the / menu so only Claude runs it
allowed-toolsTools Claude can use without asking during that turn
model, effortOverride the model or effort level for that turn
context: forkRuns the skill in a forked subagent; pair with agent to pick the type
pathsGlobs that limit automatic activation to matching files
hooksHooks registered while the skill runs

Unknown fields are ignored without an error, which is why a typo like allowed_tools fails silently. Use hyphens.

disable-model-invocation: true is the one I'd reach for first. Anything with side effects (deploying, publishing, sending email) should only run when you type the command, not when Claude decides mid-task that now is a good time to deploy.

context: fork is the other big one. A research or audit skill that reads 40 files will flood your main context. Forked, it runs in a subagent and returns a summary.

Arguments and live context

$ARGUMENTS expands to everything typed after the command. $0, $1 and so on pick positional arguments, with shell-style quoting:

---
name: migrate-component
description: Migrate a component between languages or frameworks.
argument-hint: [component] [from] [to]
---

Migrate the $0 component from $1 to $2. Keep behavior and tests identical.

/migrate-component SearchBar JavaScript TypeScript fills all three. If no placeholder uses the arguments, Claude Code appends them as ARGUMENTS: <value> so nothing is lost. Write a literal dollar sign as \$1.00.

The !`command` syntax runs a shell command before Claude sees the skill and pastes the output in. It's how a skill starts with real state instead of asking Claude to go look. Two gotchas. A failing command aborts the whole invocation, so append || true to anything that might exit non-zero, like a grep with no matches. And those commands go through your permission rules, so pre-approve them in allowed-tools, for example Bash(git diff *).

${CLAUDE_SKILL_DIR} points at the skill's own folder, so bundled scripts work no matter where the skill is installed.

The pattern I use everywhere: a thin skill over real files

My best skills are short and point at files that hold the truth. The events digest skill names its home repo and four files: a preferences file to read first, a list of sources with tested fetch recipes, a render script that builds the email ("never hand-write email HTML"), and a folder of past digests. The skill itself is mostly "read these, in this order, and never do X".

That keeps the skill stable while the knowledge changes. When my preferences change I edit one Markdown file, not the skill. It's the same idea as the charters that steer my scheduled agents: the instructions stay small, and the facts live where a person or an agent can update them.

Skills also work headless. Put /skill-name at the start of a claude -p prompt and Claude Code expands it before running, and the GitHub Action accepts a skill as its prompt input. My scheduled jobs run on a short prompt that says to read a charter and follow it, and it names any skill the agent must load, like my writing-voice skill before anything gets posted.

What I'd do next

Skills tell Claude how to do a job; they don't stop it doing the wrong one. For hard limits, see Claude Code hooks. For skills that do heavy reading, Claude Code subagents covers when to fork the work.

Questions people ask

What is a Claude Code skill?

A skill is a folder with a SKILL.md file containing YAML frontmatter and Markdown instructions. Claude sees its description at startup and loads the full instructions when a request matches or when you type the skill's slash command.

Where do I put Claude Code skills?

Personal skills go in ~/.claude/skills/name/SKILL.md and apply to every project. Project skills go in .claude/skills/name/SKILL.md inside the repo and can be committed. Plugins can also ship skills.

Why isn't my Claude Code skill triggering?

Usually the description. Put the main use case first, quote the phrases people actually type, keep it to one job, and stay under the 1,536-character listing limit. Run /skill-doctor to see which skills are loaded but never used.

What's the difference between skills and slash commands?

Both create a /name command and a skill wins if the names collide. Skills add a folder for supporting files, control over who can invoke them, and automatic loading when the description matches.

How do I stop Claude from running a skill automatically?

Add disable-model-invocation: true to the frontmatter. You can still run it with its slash command, but Claude won't load it on its own. Use this for anything that deploys, publishes or sends.

Can I use skills with claude -p?

Yes. Start the prompt with /skill-name and Claude Code expands the skill before running. In GitHub Actions you can pass the same /skill-name as the prompt input after checking out the repo.