# Claude Code Skills Tutorial: Build Your First Skill (2026)

> Claude Code Skills turn a repeated prompt into a folder Claude reaches for on its own. Build one real Skill and run it in this hands-on tutorial.

*Source: https://www.infowok.com/claude-code-skills-tutorial/ · Sukhveer Kaur · Published August 1, 2026*

---

You keep pasting the same three-paragraph prompt into Claude Code every day. "Write a PR description. Pull the ticket number from the branch name. Flag anything risky in the diff." **Claude Code Skills** turn that repeated prompt into a folder Claude reaches for on its own. It takes about ten minutes to build your first one.

This guide builds one real Skill from scratch: `pr-notes`, a Skill that reads your uncommitted changes, writes a PR description, and flags risks. That's the exact job a lot of developers still do by hand before every pull request. By the end you'll have it running. You'll also know the one mistake that makes most first Skills silently never fire.

<KeyTakeaways>

- **A Skill is just a folder with a `SKILL.md` file** — YAML frontmatter that tells Claude when to use it, plus markdown instructions for what to do.
- **The `description` field is the trigger, not documentation.** Claude matches your prompt against that text, so a vague description means the Skill never loads.
- **You'll build one real, reusable Skill in this guide**, run it against an actual diff, and fix the exact reason first-timers' Skills go quiet.

</KeyTakeaways>

<Prerequisites>

- Claude Code installed and working — see [Run Claude Code for Free](/run-claude-code-for-free-2026/) if you haven't set it up yet
- A local git repository you can make a throwaway edit in
- Comfort creating a folder and a text file — that's the entire skill requirement

</Prerequisites>

## What Claude Code Skills Actually Are

**A Skill is not magic — it's a directory Claude reads on demand, not a special model feature.** Every Skill needs exactly one required file, `SKILL.md`. That file splits into two parts: YAML frontmatter between `---` markers that controls *when* Claude uses it, and markdown content underneath that Claude follows once it loads.

Two things happen at different times, and mixing them up is where the confusion starts:

- **Always in context:** the Skill's `name` and `description` — a short listing so Claude knows what's available.
- **Loaded only when used:** the full body of `SKILL.md` — the actual instructions. These only enter the conversation when the description matches your request, or you invoke the Skill directly.

That second point is what makes Skills cheap. A CLAUDE.md file gets read into every session, whether you need it or not. A Skill's instructions cost nothing until Claude actually decides to use them. [Per the official docs](https://code.claude.com/docs/en/skills), that's the whole reason to move a growing checklist out of CLAUDE.md and into a Skill.

![Anatomy of a Claude Code Skill: SKILL.md, optional scripts/ and examples/, and how the description field decides whether the full file loads](./claude-code-skills-anatomy.svg)

The diagram is the entire mental model: **your description is a filter, and everything past it only exists once the filter passes.**

<Callout type="note">

Skills follow the open [Agent Skills standard](https://agentskills.io), so the same `SKILL.md` format works across other tools that adopt it, not just Claude Code.

</Callout>

## Build It: A Real, Reusable Skill

**Skip the toy example — build something you'd actually keep.** We'll make `pr-notes`, a Skill that turns your uncommitted diff into a PR description with a risk checklist. It uses a `scripts/` helper to pull the ticket number out of your branch name.

Start with the folder. Project Skills live in `.claude/skills/` and get committed with your repo, so your whole team gets them:

```bash
mkdir -p .claude/skills/pr-notes/scripts
```

Now write `SKILL.md`. This is the file that matters most. Get the `description` right and everything else follows:

```yaml
---
name: pr-notes
description: Drafts a PR title, description, and risk checklist from your
  uncommitted git changes. Use when the user asks for a PR description, a
  commit summary, or "what should I put in this pull request."
---

## Current changes

!`git diff HEAD`

## Ticket ID

!`bash .claude/skills/pr-notes/scripts/branch-ticket.sh`

## Instructions

Using the diff and ticket ID above, write:

1. A one-line PR title, prefixed with the ticket ID if one was found
2. A 2-4 sentence description of what changed and why
3. A short risk checklist: missing error handling, missing tests, hardcoded
   values, or anything that touches auth or payments

If the diff is empty, say there is nothing to summarize.
```

Then add the helper script it references, `scripts/branch-ticket.sh`. It's a plain shell script that pulls a ticket ID like `PROJ-482` out of your current branch name:

```bash
#!/usr/bin/env bash
branch=$(git branch --show-current)
ticket=$(echo "$branch" | grep -oE '[A-Z]{2,}-[0-9]+' | head -1)
if [ -n "$ticket" ]; then
  echo "$ticket"
else
  echo "no ticket ID found in branch name"
fi
```

Two things are worth noticing here. The `` !`command` `` syntax is **dynamic context injection**. Claude Code runs that command and swaps the line for its output before Claude ever reads the file. The instructions arrive with your actual diff already filled in, not a placeholder Claude has to guess at.

And `scripts/branch-ticket.sh` sits in its own file on purpose, so it gets *executed*, not loaded into context. A growing Skill should keep the main file lean and push heavier logic into supporting files — the [official guidance](https://code.claude.com/docs/en/skills) recommends keeping `SKILL.md` under 500 lines.

<Callout type="tip">

Make the script executable once — `chmod +x .claude/skills/pr-notes/scripts/branch-ticket.sh` — and it runs the same way every time the Skill fires.

</Callout>

**Bottom line: the whole Skill is two small files, and neither requires touching any config elsewhere.** Claude Code discovers it from the folder alone.

## Run It: How Claude Decides to Load Your Skill

**This is the step most tutorials skip, and it's the one that actually explains why Skills sometimes go quiet.** Make a small edit in your repo. Then open Claude Code in that project and try it two ways.

Let Claude pick it up on its own, by asking something close to the description:

```text
Draft a PR description for what I just changed
```

Or skip the guesswork and invoke it by name:

```text
/pr-notes
```

Either way, you should see a title, a short description, and a risk checklist land in the response, built from your real diff. I ran `branch-ticket.sh` on its own first, on two throwaway branches — one named after a ticket ID, one not — and it printed the right ticket ID in the first case and a clear "no ticket ID found" fallback in the second. That's the same check worth running before you trust the Skill: verify the script's logic before you verify the Skill's trigger.

The trigger itself is the part I rewrote. My first draft of the description just said "helps with pull requests" — technically accurate, but not close to what anyone actually types. The [official troubleshooting guidance](https://code.claude.com/docs/en/skills) is blunt about the fix: check whether the description includes the words a user would naturally say. I rewrote mine to spell out "PR description," "commit summary," and the literal phrase "what should I put in this pull request" — the version you saw above — because those are closer to what actually shows up in a real prompt.

<Callout type="warning">

If the frontmatter YAML is malformed — a missing colon, bad indentation — Claude Code still loads the Skill body with empty metadata. `/pr-notes` keeps working, but Claude has no description to match against, so it never triggers automatically. Run with `--debug` if a Skill seems to vanish.

</Callout>

![Five-step flow from writing SKILL.md to Claude loading it, following the steps, and the one warning that explains most triggering failures](./claude-code-skills-flow.svg)

## Make It Reusable: Scope, Arguments, and Control

**A Skill that only works in one repo isn't much better than the prompt you were pasting before.** Three frontmatter fields decide how far `pr-notes` travels and who can fire it.

**`disable-model-invocation: true`** restricts a Skill to manual `/name` invocation only. That's the right call for anything with side effects, like a `deploy` or `commit` Skill you don't want Claude deciding to run because your code "looks ready." **`allowed-tools`** pre-approves specific tools for the turn that invokes the Skill, so a Skill that shells out to `git` doesn't stop and ask permission every time.

**Personal vs. project placement** decides reach. `~/.claude/skills/pr-notes/` follows you across every project on your machine. `.claude/skills/pr-notes/` in a repo ships with the codebase for your whole team instead.

I keep `pr-notes` project-scoped and leave model invocation on, because drafting a PR description has no real side effects. Worst case, Claude writes one nobody asked for and I ignore it. A `deploy` Skill in the same repo gets `disable-model-invocation: true` — I don't want an eager agent shipping to production just because the diff "looked done."

<Callout type="key">

**One `description` field decides whether Claude ever picks up your Skill on its own.** Everything else in this section is about scope and safety once it does.

</Callout>

## When to Reach for a Skill vs. a Subagent

A Skill adds knowledge or a procedure to the conversation Claude is already having with you. Think of it as reference material or a checklist, not a separate worker. A subagent is a different thing: its own context window, running its own task, reporting back a summary instead of a stream of tool calls in your main thread.

`pr-notes` belongs as a Skill because it's a quick, single-turn job. A task like "research every open dependency vulnerability across five services" belongs in a subagent instead, because it needs room to explore without flooding your main conversation. If you're not sure which one a given workflow needs, that decision deserves its own deep dive — see the read-next link below.

## Testing It and Common Mistakes

**Verify the basics before you trust a Skill in daily use.** Run `/pr-notes` on a repo with real uncommitted changes. Confirm the ticket ID, title, and risk list all reflect your actual diff, not a hallucinated one. Then deliberately break it once: rename the branch to something without a ticket pattern, and confirm the script prints "no ticket ID found" instead of erroring.

Three mistakes explain almost every "my Claude Code Skills won't trigger" report:

- **Vague description.** "Helps with PRs" matches nothing specific. "Drafts a PR title, description, and risk checklist from your uncommitted git changes" matches the words people actually type.
- **Wrong folder.** A Skill dropped in the repo root instead of `.claude/skills/<name>/SKILL.md` simply isn't discovered. Claude Code only watches the documented locations.
- **Over-broad scope.** A Skill that tries to handle PRs, deploys, and changelog generation all at once ends up with a description too generic to match reliably on any one job. One job per Skill.

## What to Build Next

I'd extend `pr-notes` next with a second script that checks whether the diff touches any file matching `**/migrations/**`, and adds a dedicated "database migration" line to the risk checklist. That's the one category of change that's bitten me hardest in code review. If your team has a recurring checklist — security review, accessibility, a release runbook — that's the shape of your next Skill: one folder, one clear job, and a description written in the exact words people already use to ask for it.

<KeyTakeaways>

- Two files were enough: `SKILL.md` for the trigger and instructions, `scripts/branch-ticket.sh` for the one piece of logic that didn't belong inline.
- The description field does all the triggering work — write it in the words your team actually says.
- Keep one job per Skill, and split anything that's trying to do two things.

</KeyTakeaways>

**Common mistakes, recapped: vague description, wrong folder, over-broad scope — in that order of how often they bite.** Fix the description first. It solves most "my Skill never runs" reports on its own.

What repeated prompt are you turning into a Claude Code Skill first?

Read next: **Claude Code: Skills vs Subagents vs Agent Teams** (coming soon) covers the fuller decision between the three, for when a Skill alone isn't enough. Related: [Claude Code MCP with Local Models](/claude-code-mcp-local-models/) for pairing Skills with MCP tools, and [What Is an MCP Server?](/what-is-mcp-server-complete-guide-2026/) if you're deciding between building a Skill or an MCP tool for a given job.
