SSD Nodes Learn 🎉 VPS from $5.50/mo
Guides Matt ConnorBy Matt Connor

How to Write Your Own Agent Skill

Write your own agent skill by distilling one real failure: the SKILL.md anatomy, the description line that decides when it fires, and how to test it.

Write your own agent skill from one real failure

The best way to write your own agent skill is to distill it from one real failure. Find a task your coding agent got wrong twice, write down the correction you typed both times, and save that correction as a SKILL.md file the agent can load by itself. Everything after that is mechanics: the file layout, and the one line that decides whether the skill ever fires.

That order matters. A skill written from imagination documents a problem you never had, and it still costs context in every session. A skill distilled from a failure you watched arrives with its own test attached: ask the same thing again, and see whether the agent gets it right this time. If the format itself is new to you, read what agent skills are and how an agent loads them first, then come back and write one.

Start from a task the agent got wrong twice

Once is chance. Twice is a pattern, and a pattern is worth a file.

Here is a failure that repeats on real servers. You ask the agent to add a reverse proxy block to nginx. It edits /etc/nginx/conf.d/app.conf, then runs sudo systemctl restart nginx. The edit has a typo, so nginx refuses to start, and the site is down until you fix it:

nginx: [emerg] unknown directive "proxy_pas" in /etc/nginx/conf.d/app.conf:12
Job for nginx.service failed because the control process exited with error code.

You correct it in chat. Test the config with sudo nginx -t before touching the service, then apply it with reload instead of restart. A week later, on a different task, the same mistake. That second time is the signal.

Write down two things while the failure is still in front of you: the request you typed, and the correction you gave, in the words you used. Those two lines become the skill. The request tells you what the trigger has to match. The correction is the whole content.

Anthropic's own authoring guidance puts this first. Run the agent on representative tasks with no skill, record where it fails, then write the minimum instructions that fix those failures. The failures are the specification, so a skill you cannot trace back to one is usually a skill nobody needed.

The anatomy of a skill

A skill is a directory with one required file in it.

.claude/skills/nginx-config-changes/
├── SKILL.md
├── reference/
│   └── proxy-headers.md
└── scripts/
    └── check-and-reload.sh

SKILL.md opens with a frontmatter block, a few settings written in YAML (the same configuration format Docker Compose files use) between --- markers, followed by the instructions in markdown. Here is the whole skill for the failure above.

---
name: nginx-config-changes
description: Tests and reloads nginx safely after a config edit. Use when editing files under /etc/nginx, adding a server block or a reverse proxy, or changing a TLS certificate path.
---

## Rules

Run `sudo nginx -t` after every edit under `/etc/nginx`. Do not touch the service until it prints `test is successful`.

Apply the change with `sudo systemctl reload nginx`. Never use `restart`. A reload keeps the running workers serving traffic until the new config parses, so a broken config leaves the site up. A restart stops nginx first, so a broken config takes the site down.

If `nginx -t` fails, fix the file and test again. Never reload a config that failed the test.

For the proxy header defaults this project expects, see [reference/proxy-headers.md](reference/proxy-headers.md).

That file is under twenty lines and it is a complete skill. The parts:

  • name: up to 64 characters, lowercase letters, digits and hyphens only, and it cannot contain the words claude or anthropic. In a personal or project skill this is only the display label. The command you type comes from the directory name, so this one answers to /nginx-config-changes.
  • description: what the skill does and when to use it, up to 1,024 characters. This line does the real work, and the next section is about nothing else.
  • The body: the instructions, loaded only when the skill actually fires.
  • reference/: extra files the agent reads on demand. Link them from SKILL.md and keep the links one level deep, because a file referenced from another referenced file often gets read only in part.
  • scripts/: files the agent executes rather than reads. Only their output costs context, so a 300 line script is cheap.

Where you put the directory decides who gets the skill.

  • .claude/skills/<name>/SKILL.md in the repository: this project only, and it travels to everyone who clones the repo.
  • ~/.claude/skills/<name>/SKILL.md: every project on your machine, and nobody else's.
  • <plugin>/skills/<name>/SKILL.md: shipped inside a plugin, available wherever that plugin is enabled.

Create one with mkdir -p .claude/skills/nginx-config-changes and write the file. Claude Code watches these directories, so editing an existing skill takes effect inside the running session. Creating a top-level skills directory that did not exist when the session started needs a restart, because there was nothing to watch when the session began.

The description field is the highest-leverage line in the file

At startup the agent loads the name and description of every available skill into its context. It does not load the bodies. When your request arrives, that one line is the entire basis for deciding whether this skill is relevant, so a perfect body behind a vague description never gets read.

Write the description in the third person. "Tests and reloads nginx safely" works. "I can help you with nginx" does not, because the text is injected into the system prompt, where first person reads as the model talking about itself.

Carry two things in it: what the skill does, and the condition under which it applies. Put the important use case first, because Claude Code truncates the listing entry at 1,536 characters. There is an optional when_to_use field for extra trigger phrases and example requests, and it is appended to the description under that same cap.

Then use the words you will actually type. description: Helps with nginx matches nothing, because nobody types "helps with". The version above names /etc/nginx, server block, reverse proxy and TLS (transport layer security) certificate path, which is roughly the vocabulary of any request that should trigger it.

Here is the test for a description. Give that single line to somebody who has never seen the body, together with the request you are about to type, and ask them whether the skill applies. If they cannot tell, the model cannot either.

Keep the body small, because it stays in context

When a skill is invoked, its rendered content enters the conversation as one message and stays there for the rest of the session. Claude Code does not re-read the file on later turns. Every line you write is a cost you pay for the whole session, not for one answer.

Anthropic recommends keeping SKILL.md under 500 lines and moving detail into separate files. Compaction shows why that number is not arbitrary. When the conversation is summarised to free context, Claude Code re-attaches the most recent invocation of each skill, keeps only the first 5,000 tokens of each, and fills a combined 25,000 token budget starting from the skill invoked most recently. A long skill gets cut off part way through. Several long skills push each other out entirely.

So write only what the model does not already know. It knows what nginx is and what a reverse proxy does. It does not know your house rule about reload over restart, and that rule is the only reason this file exists.

If the skill tells the agent to run a bundled script, name the path with ${CLAUDE_SKILL_DIR} so it resolves wherever the skill is installed, and pre-approve the same command so the run does not stop on a permission prompt.

---
name: nginx-config-changes
description: Tests and reloads nginx safely after a config edit. Use when editing files under /etc/nginx, adding a server block or a reverse proxy, or changing a TLS certificate path.
allowed-tools: Bash(${CLAUDE_SKILL_DIR}/scripts/check-and-reload.sh *)
---

The grant covers the turn that invoked the skill and clears when you send your next message, so it does not quietly become a permanent permission.

How to prove the skill fires

Watching a skill load tells you the agent found it. It does not tell you the answer changed. Check both, and check in a fresh session, because the session where you wrote the skill already holds everything you said while writing it. That leftover context hides the gaps in the file.

  1. Start a new session with claude in the project.
  2. Type the request the way you would on an ordinary working day, in your own words, without naming the skill.
  3. Watch for the invocation. If the skill does not fire, fix the description. The body is not the problem yet.
  4. Invoke it by hand with /nginx-config-changes as a control. Correct behaviour when invoked by hand and wrong behaviour when invoked by request confirms a trigger problem rather than an instruction problem.
  5. Run the same request with the skill switched off and compare the two answers. In the /skills menu, highlight the skill, press Space to cycle its state to off, then Enter to save. That writes a skillOverrides entry into .claude/settings.local.json, and pressing Space again cycles it back to on when you are done.
  6. Write a couple of requests that should not trigger the skill, and check that it stays quiet on those.

To automate that loop, install the skill-creator plugin from the official marketplace.

/plugin marketplace add anthropics/claude-plugins-official
/plugin install skill-creator@claude-plugins-official

If the install output says Run /reload-plugins to activate., run that command. Then ask Claude to evaluate your skill by name. The plugin stores test cases in evals/evals.json inside the skill directory and runs each case in its own subagent, so every run starts with a clean context. It then writes a with-skill against without-skill comparison, which is the honest number: the pass rate improvement measured against the tokens and the time the skill costs.

Failure mode: the skill never triggers

You type the request, the agent does the old wrong thing, and no skill line appears. Work through these in order.

  • The description says what the skill does but never says when to use it, so nothing in your request matches it.
  • The description avoids the words you type. If you say "nginx", the description has to say nginx.
  • disable-model-invocation: true is set in the frontmatter. That keeps the description out of the model's context entirely, and leaves the skill invocable only by you with /name.
  • A paths glob in the frontmatter limits activation to matching files, and the file you are working on does not match.
  • The skill sits in a nested .claude/skills/ directory below your starting directory. Those load only after the agent reads or edits a file inside that subdirectory, so until then the skill is not available at all.

Failure mode: the skill triggers constantly

The opposite problem is a description so broad that the skill fires on unrelated work. "Use when working on the server" matches almost any request in a server repository. The body then loads on tasks it cannot help with, and it stays in context for the rest of the session.

Narrow the description to the condition that actually matters, and name the files or the commands it covers. Add a paths glob when the skill only applies to certain files. For anything with side effects, such as a deploy or a commit, set disable-model-invocation: true and invoke it yourself with /name, so the agent never decides on its own that now is a good moment to deploy.

Failure mode: the skill belongs in your rules file

A rules file such as CLAUDE.md or AGENTS.md loads at the start of every session and applies to every task. A skill body loads only when the skill fires. Frequency is the whole decision. A fact that holds for every task in the repository, like the package manager you use, belongs in the rules file. A procedure that applies to a small slice of tasks, like the nginx rule above, belongs in a skill, where it costs nothing on the days nobody edits nginx.

The real failure is putting it in both places. Two copies drift apart, and when the agent does the wrong thing you cannot tell which copy it followed. Pick one home for each instruction. the boundary between skills, MCP servers and rules files works through the harder cases, including when the right answer is an MCP (model context protocol) server that hands the agent a new tool rather than a new instruction.

Share it once it has earned its place

A skill that survives a week of real work is worth committing. Project skills in .claude/skills/ are reviewed like code and arrive with the repository, so a teammate who clones it gets your correction with no setup step. Moving a skill between repositories without copy and paste is its own problem, covered in how to share agent skills across repos.

One portability note. Claude Code accepts a long list of frontmatter fields, but the Agent Skills standard allows only six: name, description, license, compatibility, metadata and allowed-tools. Upload a skill to claude.ai, or package it for the Skills API, with anything else in the frontmatter, and it fails outright instead of ignoring the field:

Unexpected key(s) in SKILL.md frontmatter: argument-hint. Allowed properties are: allowed-tools, compatibility, description, license, metadata, name

Stay inside those six fields and the same file loads in Claude Code and in everything else that reads the standard. Writing the instructions themselves so they survive the move to a different model is a separate job, and writing skills that work with any model covers it.

FAQ

How long should a SKILL.md file be?

Keep it under 500 lines, and expect most useful skills to be far shorter than that. The body enters the conversation when the skill is invoked and stays there for the rest of the session, so every line is a recurring cost rather than a one-time one. Move long reference material into separate files in the skill directory and link them from SKILL.md, one level deep, so the agent reads them only when it needs them. Bundled scripts are executed instead of read, so they cost only their output.

Why does my skill never trigger?

The description is the usual cause, because it is the only part of the skill in context when the model decides. Make sure it says when to use the skill, not only what it does, and that it contains the words you actually type in your requests. If the description looks right, check the frontmatter for disable-model-invocation: true, which hides the skill from the model completely, and for a paths glob that limits it to files you are not touching. A skill in a nested .claude/skills/ directory below your starting directory is another cause: it loads only after the agent reads or edits a file in that subdirectory.

Should this be a skill or a line in my rules file?

Ask how many of your tasks it applies to. A rules file loads in every session, so it should hold facts that are true for every task, such as the package manager or the branch naming convention. A skill loads only when it fires, so it is the right home for a procedure that matters on a small share of tasks. Never write the same instruction in both places, because the two copies drift and you lose the ability to tell which one the agent followed.

How do I know a skill actually helped?

Compare it against a baseline. Collect a few real requests, run each one in a fresh session with the skill available, then run them again with the skill switched off from the /skills menu, and read both answers side by side. A fresh session matters because the conversation where you wrote the skill still contains your explanations, which makes an incomplete file look complete. The skill-creator plugin runs this comparison for you and reports the pass rate next to the token cost.

Can I use the same SKILL.md with a different agent?

Yes, as long as you stay inside the fields the Agent Skills standard defines: name, description, license, compatibility, metadata and allowed-tools. Claude Code accepts many more fields, and it also supports body features such as shell command injection that other tools do not run. Uploading a skill with a field outside the standard fails with an explicit error listing the allowed properties, so decide early whether a skill is meant to stay in Claude Code or travel.