All posts
Article
ClaudeDeveloper ToolsAI

Writing a Claude Code Skill: a step-by-step guide

Aug 30, 2026 6 min readby Tuan Nguyen

A Claude Code skill is a packaged set of instructions that loads into the model's turn when invoked, replacing the default approach with a domain-specific workflow. Think of it as a reusable prompt component: write the instructions once, invoke them with /skill-name, and Claude follows that playbook instead of improvising.

This post walks through authoring one, end to end, using a code-review skill as the worked example — the kind of internal tooling I build at work to keep review quality consistent across a team.

1. What a skill actually is

A skill is a folder on disk with a SKILL.md file inside it. That's the minimum. The file is Markdown with YAML frontmatter describing when and how the skill should run. When you type the skill's name, the harness loads SKILL.md into the conversation context and Claude follows it.

.claude/skills/
└── code-review/
    └── SKILL.md

That's the whole structure. No build step, no registry, no package manifest. You write Markdown; Claude reads it.

2. The frontmatter contract

The frontmatter tells the harness what the skill is and when to offer it. The fields that matter:

---
name: code-review
description: Review the current diff for correctness bugs and reuse/efficiency cleanups. Runs at a configurable effort level; pass --fix to apply findings.
allowed-tools: Read, Grep, Glob, Bash, Edit
---

allowed-tools scopes what the skill can do. A review skill needs to read files and search (Read, Grep, Glob) and, if it can apply fixes, edit (Edit) and run commands (Bash). Scoping tools is both a safety rail and a hint — it tells the model what the skill is for.

3. Writing the instruction body

The body of SKILL.md is the actual workflow. This is where you encode the playbook. The best skills read like a senior engineer's review checklist — specific, ordered, and explicit about tradeoffs. Here's the structure I reach for:

## Steps

1. Identify the diff. Run `git diff` to find changed files; if a branch or
   PR is named, resolve it to a diff range. Skip generated files
   (.next/, node_modules/, lockfile churn).
2. For each changed file, read enough surrounding context to understand the
   change — not just the diff hunk. A one-line edit in a state machine needs
   the whole machine.
3. Review for correctness first: off-by-one, null/undefined, race conditions,
   broken invariants, wrong boundary conditions.
4. Then review for reuse/simplification: duplicated logic, reinvented helpers,
   dead code, unnecessary abstraction.
5. Rank findings by severity. Only report the ones you'd act on — a nitpick
   flood is noise. If effort is `low`/`medium`, keep only high-confidence
   findings; at `high`/`max`, include plausible but uncertain ones, flagged.
6. If --fix was passed, apply the fixes and show the diffs. Otherwise, report
   findings as a ranked list with file:line anchors.

A few principles that make the body land:

Be imperative, not descriptive. "Run git diff" beats "you should look at the changes." Claude follows instructions; instructions are verbs.

Encode the stop conditions, not just the go conditions. The line "skip generated files (.next/, node_modules/, lockfile churn)" is what stops the skill from reviewing 10,000 lines of build output. The constraints are more valuable than the steps.

Specify the output format. "A ranked list with file:line anchors" tells Claude exactly what to hand back. Without it you get prose paragraphs that are hard to act on.

4. Handling arguments

Skills receive their invocation arguments in the turn. For a review skill, the argument is usually the effort level or a --fix flag. You handle this by telling Claude how to parse it in the instructions:

## Arguments

- No argument or `low`/`medium`: fewer, high-confidence findings only.
- `high` or `max`: broader coverage; include plausible-but-uncertain findings,
  clearly flagged as such.
- `--fix`: apply the fixes to the working tree after reporting.
- `--comment`: post findings as inline PR comments (requires gh CLI auth).

The model reads this and resolves the argument from your /code-review high --fix invocation. No argument parsing code — it's just instructions.

5. Optional: reference files

For non-trivial skills, you can add reference Markdown files alongside SKILL.md and reference them from the instructions. This keeps the main file scannable while letting deep material live in a separate file the skill can read on demand.

.claude/skills/
└── code-review/
    ├── SKILL.md
    └── references/
        └── severity-rubric.md

In SKILL.md: "For severity ranking, see references/severity-rubric.md." Claude loads the reference only when the instruction points to it — so the rubric doesn't bloat context on every invocation, just when it's relevant.

6. Invoking it

Once the folder exists, the skill is available. Invoke it by name:

/code-review              # default effort
/code-review high         # broader coverage
/code-review --fix        # apply the fixes

The harness matches the name, loads SKILL.md, and Claude runs the playbook. There's no registration step, no restart, no build. Edit the file and the next invocation uses the new version.

7. Iterating

Skills are prompts, and prompts need iteration. The loop:

  1. Invoke the skill on a real diff.
  2. Read the output. Anything it missed → add a step. Anything it got wrong → add a constraint.
  3. Anything verbose that wasn't useful → it's a sign the instruction is vague; make it specific.

The fastest way to a good skill is running it on real work and reading the failures. A skill that says "review for bugs" will miss race conditions; a skill that says "check for concurrent state mutation in async handlers and event callbacks" will catch them. Specificity is the lever.

8. When to write a skill (and when not)

Write a skill when:

  • You repeat the same multi-step workflow and want it consistent.
  • The workflow has non-obvious stop conditions Claude wouldn't guess.
  • You want team-shared review/migration/deploy playbooks.

Don't write a skill when:

  • A one-shot prompt does the job. Skills are for reusable workflows, not single tasks.
  • The task is creative or exploratory — a skill's rigidity hurts there.

The code-review skill earns its keep because review is repetitive, the checklist is stable, and the stop conditions (skip generated files, rank by severity, only report what you'd act on) are the exact things a default prompt gets wrong.

Closing

The mental model that made skills click for me: a skill is a function, SKILL.md is its implementation, the frontmatter is its type signature, and the arguments are its parameters. You write it once, invoke it by name, and it runs the same playbook every time — which, if your work is building the tooling around engineering, is a satisfying convergence. The skill that reviewed this very post is the one it describes.