ES · Leer en español Explore for free

Recipe · Basic · 10 min

Create your first Claude Code skill, step by step

Build a project skill in .claude/skills with SKILL.md, a helper script and a description Claude recognizes. Example: release notes from git log.

Tested with Claude Code 2.1.316 · reviewed

The problem

Every time you cut a release you paste the same instructions into Claude: "look at the commits since the last tag, group them into features and fixes, don't make anything up". When those instructions live in your head or in the chat, they get lost and everyone on the team gets a different result. A skill stores that procedure in the repository, so you can run it with /name or let Claude load it on its own when it's needed.

The solution

We'll build the version-notes skill, which writes Markdown release notes from git log.

1. Create the skill folder

Project skills live in .claude/skills/<name>/SKILL.md at the root of the repository. Personal skills go in ~/.claude/skills/<name>/SKILL.md and work in all your projects.

mkdir -p .claude/skills/version-notes/scripts

2. Write SKILL.md

Save this as .claude/skills/version-notes/SKILL.md. The block between --- lines is the frontmatter, and it must start on the first line of the file.

---
name: version-notes
description: Generates Markdown release notes from the git commits since the last tag. Use when asked for release notes, a changelog, or "what changed since the last version".
argument-hint: "[from-tag]"
allowed-tools: Bash(bash *version-notes/scripts/commits.sh*) Bash(git log *) Bash(git describe *)
---

# Release notes

## Commits since the last version
!`bash "${CLAUDE_SKILL_DIR}/scripts/commits.sh" $ARGUMENTS`

## Instructions
1. Group the commits above by prefix: `feat` → **New**, `fix` → **Fixes**, `docs` → **Documentation**, anything else → **Other changes**.
2. Rewrite each commit as a short, user-facing sentence (no hash or prefix), in the language of the commits.
3. Skip empty sections. Don't invent changes that aren't in the list.
4. Return only the Markdown, starting with `## Changes since <tag>`. Follow the format in [template.md](template.md).

What each field does:

FieldWhat it's for
nameThe command in the / menu. Defaults to the folder name.
descriptionWhat the skill does and when to use it. Claude reads it to decide whether to load the skill.
argument-hintThe hint shown during autocomplete, for example [from-tag].
allowed-toolsTools that skip the permission prompt during the turn that invokes the skill.

The ` !command line runs **before** Claude reads the skill, and its output replaces the line. That way Claude gets the real commits instead of having to look for them. ${CLAUDE_SKILL_DIR} is the skill's folder and $ARGUMENTS is whatever you type after /version-notes`.

Other optional fields in the official guide: when_to_use (more trigger phrases), arguments (named arguments), disable-model-invocation: true (only you can invoke it), user-invocable: false (only Claude can invoke it), disallowed-tools, model, effort, context: fork with agent (run it in a subagent), hooks, paths (activate it only for certain files) and shell. All of them are optional; Claude Code silently ignores field names it doesn't recognize.

3. Add a helper script

Scripts are executed, not loaded into context. Save this as .claude/skills/version-notes/scripts/commits.sh:

#!/usr/bin/env bash
# Lists commits since the given tag (or the latest one). With no tags, lists the whole history.
from="${1:-$(git describe --tags --abbrev=0 2>/dev/null)}"
if [ -n "$from" ]; then
  echo "Starting tag: $from"
  git log --no-merges --pretty='- %s (%h)' "$from"..HEAD
else
  echo "Starting tag: (none, full history)"
  git log --no-merges --pretty='- %s (%h)'
fi
chmod +x .claude/skills/version-notes/scripts/commits.sh

4. Add a reference file

Supporting files keep SKILL.md short: Claude reads them only when it needs them, because SKILL.md links to them. Save this as .claude/skills/version-notes/template.md:

## Changes since v1.2.0

### New
- Export your favorites to CSV.

### Fixes
- Logging out no longer leaves the session open.

You end up with this layout:

.claude/skills/version-notes/
├── SKILL.md
├── template.md
└── scripts/
    └── commits.sh

5. Invoke it

Inside Claude Code type /version-notes or, to start from another tag or commit, /version-notes v1.0.0. It also works outside a session in non-interactive mode:

claude -p "/version-notes"

No restart needed: Claude Code watches the skill folders and applies changes to the open session. If you created the .claude/skills/ folder while the session was already open, run /reload-skills.

6. Write a description that makes Claude use it

Claude only sees each skill's name and description until it decides to load it. To get it picked:

With that description, the prompt "what changed since the last version? give me the changelog" made Claude invoke the skill on its own, without typing /version-notes.

Check it

Tested with Claude Code 2.1.316 in a test repository with the tag v0.1.0 and two later commits (feat: export favorites to CSV and docs: explain installation in the README):

claude -p "/version-notes"

Actual output:

## Changes since v0.1.0

### New
- Export your favorites to CSV.

### Documentation
- The README now explains how to install the app.

With an argument (claude -p "/version-notes <first-commit-hash>") the fix commit and the Fixes section show up too. The exact wording changes a little on each run; the commits included don't.

Why it works

At startup Claude Code loads only the name and description of each skill. The body of SKILL.md enters the context when you invoke it with /version-notes or when Claude decides the description matches your request, and it stays in the conversation from then on. Before handing it to Claude, Claude Code runs the ` !… ` lines and puts their output in their place. That's why a skill costs no context until you use it.

Skill, CLAUDE.md and subagent, according to the official documentation:

When it loadsWhat it's for
CLAUDE.mdAlways, at session startFacts and standing project conventions
SkillDescription always; body only when invokedProcedures, checklists, reference material
SubagentIn its own separate contextDelegated work with its own tools and instructions

The guide's rule of thumb: create a skill when you catch yourself pasting the same instructions into the chat, or when a CLAUDE.md section has turned into a procedure rather than a fact. If you want the skill to run in isolation, context: fork runs it in a subagent, which doesn't see your conversation. Docs: Skills and Subagents.

Common mistakes

FAQ

What's the difference between a skill and a command in .claude/commands?

Custom commands were merged into skills: .claude/commands/deploy.md and .claude/skills/deploy/SKILL.md both create the same /deploy. A skill adds a folder for supporting files and automatic loading by Claude; if both share a name, the skill wins.

Do I need to restart Claude Code after editing a skill?

No. Claude Code watches the skill folders and applies changes to the open session. You only need /reload-skills when you create a top-level skills folder for the first time.

How do I stop Claude from running it on its own?

Add disable-model-invocation: true to the frontmatter. Then it only runs when you type /version-notes, which is a good idea for skills with side effects, such as deploying or publishing.

Can I share it with my team?

Yes. Commit the .claude/skills/ folder to the repository, and anyone who opens Claude Code in that project gets /version-notes.

How long should SKILL.md be?

The official guide recommends under 500 lines. Put the details in supporting files linked from SKILL.md, which Claude reads only when it needs them.

All recipes