Skip to content
Back to Published Articles
Claude Code

Writing a Claude Code skill your team actually reuses

A skill is a folder with a markdown file, and the description line is the whole product. How repeated agent instructions became skills my team runs without me.

Writing a Claude Code skill your team actually reuses
100% Human-Authored: All engineering insights, production war stories, and code were written by Marcelo Costa with zero AI involvement in authoring. AI was used solely for multilingual translations.

The first description I wrote for my test-authoring skill was "Authors and maintains specifications for end-to-end coverage." Proper, precise, the kind of line that looks great in a design doc. Nobody types that. (I wrote it, so I really should have known better 😅)

And it didn't matter what I put underneath it: that one line on top decides whether the skill ever gets loaded at all.

I've written before about using a Claude Code skill to get a Cypress 16 deprecation migration done on time. That one was a personal skill, kept out of the repo. The surprise came from the skills I did put in the repo: they kept getting used by people who were not me, on tasks I had not anticipated.

Most skills never get that far. They get written once, fire on the wrong prompts (or stay quiet on the right ones), and everyone goes back to pasting instructions into chat.

So let's get to the grain here: what separated the skills that stuck from the ones written once and abandoned.

Less than you'd expect

A skill is a directory with a markdown file. That's it:

.claude/skills/
  cypress-author/
    SKILL.md
    references/
      selectors.md

And SKILL.md is frontmatter plus prose:

---
name: cypress-author
description: Creates, updates, and fixes Cypress tests (E2E and component). Use when the user asks to create tests, add tests, write tests, test this file, new spec, or fix a failing or flaky test. Applies even when the user does not say "Cypress".
---

## Non-negotiables
## Steps
## Verify

That's the entire mechanism. The agent sees the name and description at all times, and it only loads the body when it decides the skill is relevant. So guess where the engineering effort belongs? Yep, in that description.

The description is the whole product

Everything else in the file is wasted if the skill never fires. And a skill that fires when it shouldn't is worse than one that never fires, because now it's distorting unrelated work.

Two rules got my trigger accuracy up sharply.

1. Write the description in your users' words, not yours

Remember my design-doc line from the top? People don't talk like that. They type "write tests for this file", "add a test", "this spec is flaky". So the description now uses exactly that vocabulary. What you're writing is the match target for a request you haven't seen yet, and a polished summary for a human reader is the wrong tool for that job.

2. Cover the requests where the trigger word is missing

"create tests for this component" has no framework name in it. In a repo with one test framework, it's still unambiguously your skill's job. So say it out loud: I ended the description with "Applies even when the user does not say Cypress" and it changed behavior immediately.

While you're at it, also tell the skill when to stand down. If you have a companion skill for reviewing tests without changing code, say "prefer that one when the user only wants an explanation". You'd think the competition is the model's default behavior. Nope: ambiguity between two of your own skills is a much more common failure.

My one clever skill

My first attempt bundled authoring, review, and re-run planning into one skill, with branches (clever, right?). It behaved badly at all three, because the loaded instructions for the other two jobs were context noise pulling in different directions.

So I split it into three skills — author, review, rerun-plan — each with its own narrow description. Better on every axis. And they compose naturally: the agent authors with one → then picks up the rerun-plan skill on its own, because that description matches what just happened.

Here's my rule of thumb now: if your skill body needs an if the user wants X... otherwise... at the top level, you have two skills.

Where the agent gets "helpful"

Negative constraints are where agents drift. The instructions that earn their place are almost all prohibitions. The model often gets the positive instructions right unaided; the drift is in what it helpfully does in addition.

These are the lines that changed outcomes the most in my test-fixing skill:

  • Do not run the suite. Print the command and stop. (Only the human knows whether the environment is in a fit state.)
  • Do not remove an .only flag. It is there deliberately.
  • Do not suggest code that was deleted in this session.
  • Do not refactor adjacent code, comments, or formatting.
  • If a helper already exists for this, use it. Do not write a second one.

Every single one of those is there because I watched the agent do the opposite and had to correct it. And that, as it turns out, is the actual authoring loop.

Run it, watch it drift, tighten one line

You can't write a good skill up front. You write a mediocre one (mediocre is fine here, really) and let real tasks tell you where it's wrong.

  1. Write the smallest version that could work.
  2. Run it on a real task, not a toy one.
  3. Note the first place the output diverged from what you wanted.
  4. Add or tighten exactly the one instruction that would have prevented it.
  5. Repeat.

One divergence per iteration. Rewrite three instructions at once and you learn nothing about which of them mattered. And skills pile up cargo-cult lines fast: text that's there because someone once had a bad run, and now nobody can say whether removing it changes anything.

For anything you'll lean on, put a number on it. Run the skill against ten representative tasks and count how many needed correction. It takes an afternoon, it tells you whether iteration 6 is actually better than iteration 5, and it's the only defense against a skill that feels better just because you wrote it.

"Looks right" is not done

The most common failure of a skill that already works is stopping at "looks right". Why? Because the model has no way to know your definition of done unless it's written down.

So the skill ends with a verify section, and it's mechanical on purpose:

## Verify
- Run the type checker on the changed files. It must pass.
- Run the linter on the changed files. It must pass.
- Print the exact headless command for the affected specs.
- List every other spec that touches the helpers you changed.

That last line is the one people notice. "Which other tests did I just put at risk" is a question with a real answer that nobody enjoys working out by hand. Exactly the sort of mechanical thoroughness you want to encode once and stop thinking about.

Back to the migration skill

For the record, since that's the post this one follows on from. A large deprecation sweep isn't hard. It's tedious in a very specific way: repetitive judgment. Thousands of call sites, each one needing a decision that's 90% mechanical and 10% "this one is different". Three things had to be in the skill:

1. A classification rule for the cases that differ:

  • sensitive value → must move to a different mechanism
  • plain value → can be rewritten in place

Without a written rule you get an inconsistent codebase, and that's worse than either choice applied uniformly.

2. An invariant to preserve. Command order matters in this framework, and a rewrite that reorders is silently wrong. Stating the invariant is what stopped it happening.

3. A verification sequence to run before declaring victory: search for surviving old calls → switch on the setting that turns any leftover call into a hard runtime failure → let CI be the judge. Without it, "looks finished" would have been the finish line.

None of that is AI-specific. It's the checklist you'd hand a contractor doing the same sweep. And that's the mental model that made these skills useful to me: a skill is onboarding documentation that executes. If it wouldn't help a competent new hire, it won't help the agent either.

Out of my home directory, into the repo

Last practical note: keep them tool-agnostic. I keep skills in .agents/skills/ in the repo and symlink them into the tool-specific directory:

ln -s ../../.agents/skills/cypress-author .claude/skills/cypress-author

They're plain markdown with no vendor-specific syntax in the body. So the same instructions serve whichever assistant a teammate prefers, and they live in the repo, where they get reviewed in pull requests like anything else.

Skills that live in one person's home directory are that person's productivity trick. Skills in the repo are team infrastructure. And that's exactly where mine ended up paying off: the ones in the repo are the ones my team runs without me, on tasks I never had in mind when I wrote them. Not bad for a folder with a markdown file in it.

What about you? Do your skills live in the repo or in your home directory, and what's the worst description you've ever written for one? Tell me in the comments. Mine is already at the top of this post, so you're in good company!