Skip to content

Build a Claude Code Skill End to End: SKILL.md to Evals

Core Concept LearningOctober 18, 202615 min read

A Claude Code skill is a folder with a SKILL.md file: YAML frontmatter that says what the skill is for, and markdown instructions Claude follows when it runs. You can type /skill-name to run it, and Claude can load it on its own when your request matches its description. The documentation's rule of thumb for when to write one: you keep pasting the same instructions, checklist, or procedure into chat, or a section of CLAUDE.md has grown into a procedure rather than a fact.

The reason skills scale better than CLAUDE.md is how they load. Only the description sits in context all the time; the body loads when the skill is invoked, and supporting files load only if Claude reads them. Long reference material costs almost nothing until it is needed.

This guide builds one real skill end to end, a pr-ready check that runs before you open a pull request, and covers each piece on the way: where skills live, frontmatter, descriptions that trigger correctly, injected live context, a bundled script that runs without a permission prompt, invocation control, running in a subagent, and how to evaluate whether the skill helps. Everything is checked against the Claude Code skills documentation. For what belongs in CLAUDE.md instead, see How to Write a CLAUDE.md; for ready-made skills, see Top Claude Skills.

How a skill loads: only its description sits in context; the body loads when you or Claude invoke it, after injected commands run; supporting files load only if read
How a skill loads: only its description sits in context; the body loads when you or Claude invoke it, after injected commands run; supporting files load only if read

Where skills live and how they get their names

Where you save a skill decides which sessions load it:

  • Personal: ~/.claude/skills/<name>/SKILL.md, available in all your projects on this machine.
  • Project: .claude/skills/<name>/SKILL.md, loaded in sessions in this repository; commit it and your team gets it.
  • Nested: <subdir>/.claude/skills/<name>/SKILL.md, loaded once Claude works on files in that subdirectory.
  • Plugin: <plugin>/skills/<name>/SKILL.md, invoked as /plugin-name:skill-name.
  • Enterprise: deployed through managed settings to everyone in an organisation.

The command you type comes from the directory name, or from the frontmatter name field when you set one. Project skills load from .claude/skills/ in the directory where you start Claude Code and every parent up to the repository root, so a session started in packages/frontend/ still sees root skills.

Older custom commands still work: a file at .claude/commands/deploy.md and a skill at .claude/skills/deploy/SKILL.md both create /deploy. New work should use the skill layout, which adds supporting files and invocation control. Claude Code skills follow the open Agent Skills standard; fields beyond the standard's six (name, description, license, compatibility, metadata, allowed-tools) are Claude Code extensions, and uploading a skill that uses them to claude.ai or the Skills API fails with an unexpected-key error.

Quick reference

  • Claude Code watches skill directories, so adding or editing a skill takes effect in the current session without a restart.
  • Don't name a skill folder synced or anthropic-skills; those names are reserved for skills synced from claude.ai.
  • Personal skills don't load in cloud sessions; enable them on your claude.ai account if you need them there.

Remember this

Put team procedures in the project's .claude/skills/, personal ones in ~/.claude/skills/, and use the directory name as the command.

Build it: a pr-ready skill

The goal: before you open a pull request, ask "is this ready?" and get a short report grounded in the actual branch: what changed, whether lint and tests pass, and what a reviewer will ask about. The folder:

1.claude/skills/pr-ready/2├── SKILL.md3├── checklist.md4└── scripts/5    └── check.sh

SKILL.md:

1---2description: Checks whether the current branch is ready for a pull request and lists what to fix. Use when the user asks if a branch or change is ready to merge, wants a pre-PR check, or asks what a reviewer will flag.3allowed-tools: Bash(${CLAUDE_SKILL_DIR}/scripts/check.sh)4---5 6## Branch summary7 8!`git log --oneline main..HEAD`9 10!`git diff --stat main...HEAD`11 12## Instructions13 141. Run `${CLAUDE_SKILL_DIR}/scripts/check.sh` and report its result.152. Read the changed files that matter for review; don't read unrelated files.163. Apply [checklist.md](checklist.md) to the diff.174. Reply with: a two-line summary, a pass/fail line for checks, and at most five concrete fixes, most important first. If everything passes, say so in one line.

scripts/check.sh runs your existing commands and exits non-zero on failure:

1#!/usr/bin/env bash2set -euo pipefail3npm run -s lint4npm test -- --silent5echo "checks passed"

checklist.md holds the long part: your team's review rules (tests for new behaviour, no secrets, migrations reversible, public API changes documented). Because it is a separate file referenced from SKILL.md, it loads only when Claude reads it.

The pr-ready skill folder: SKILL.md with frontmatter and body, a checklist loaded on demand, and a script that runs without a prompt
The pr-ready skill folder: SKILL.md with frontmatter and body, a checklist loaded on demand, and a script that runs without a prompt

Quick reference

  • !command`` lines run before Claude sees the skill and are replaced by their output, so the report starts from the real diff rather than guesses.
  • Make the script executable (chmod +x scripts/check.sh); Claude runs it, it is never loaded into context.
  • Change main to your default branch; a missing branch makes the injected command fail.

Remember this

A good first skill pairs live context from injected commands with a short, numbered procedure and a strict output format.

Frontmatter: the fields that matter

All frontmatter fields are optional, but description is the one to get right. Claude Code reads frontmatter only if the opening --- is the first line of the file, and ignores field names it doesn't recognise without an error, so a typo such as allowed_tools silently does nothing. If the YAML doesn't parse, the skill still loads with no fields set: /pr-ready works, but Claude can no longer match it automatically. claude --debug shows the parse error, and claude plugin validate .claude/skills checks every skill's frontmatter.

Two fields deserve care:

  • allowed-tools pre-approves tools for the turn that invokes the skill, then the grant clears. It does not restrict other tools. Workspace trust doesn't gate it, so a repository skill can grant itself broad access: review allowed-tools in skills you didn't write before running Claude Code in that repository. Organisations can ignore it in project and personal skills with allowManagedPermissionRulesOnly.
  • disallowed-tools removes tools while the skill is active, useful for an autonomous skill that should never, for example, ask the user a question.
Frontmatter fields you will use most (all optional)
FieldWhat it does
descriptionWhat the skill does and when to use it; Claude matches requests against it
when_to_useExtra trigger phrases, appended to the description
allowed-toolsTools Claude may use without asking, only during the invoking turn
disable-model-invocationtrue: only you can run it with /name
user-invocablefalse: only Claude can load it; hidden from the / menu
argument-hint, argumentsAutocomplete hint and named $placeholders
context, agentfork: run the skill as a subagent of the given type
pathsLoad automatically only when working with matching files
model, effortOverride for the turn (or the forked subagent)

Quick reference

  • Using ${CLAUDE_SKILL_DIR} in both the body and the allowed-tools rule makes the approved command match exactly, wherever the skill is installed.
  • The skill body stays in context across later turns but its allowed-tools grant does not; invoking it again re-applies the grant.
  • Keep frontmatter to the Agent Skills fields if you plan to upload the skill to claude.ai or the Skills API.

Remember this

Write the description carefully, spell field names exactly, and treat allowed-tools in shared skills as a permission grant to review.

Descriptions that trigger at the right time

Claude decides whether to load a skill from its description, which appears in a listing of available skills on every turn. Three practical rules follow from how that listing works:

1. Lead with the use case. The combined description and when_to_use text is truncated at 1,536 characters, and when you have many skills Claude Code drops descriptions of the least-used ones to fit a budget of about 1% of the context window. Put the key trigger in the first sentence. 2. Use the words people actually say. "Checks whether the branch is ready for a pull request" matches "is this ready to merge?" better than "Pre-merge validation workflow". 3. Say when, not only what. "Use when the user asks if a branch is ready, wants a pre-PR check, or asks what a reviewer will flag" gives Claude explicit triggers.

If the skill never triggers, check that it appears when you ask What skills are available?, rephrase your request closer to the description, and invoke it directly to confirm it works. If it triggers too often, make the description more specific, or add disable-model-invocation: true so it only runs when you type it.

Every listed description costs context on every turn whether or not the skill is used; /skill-doctor reports each skill's cost and usage so you can turn off ones you never invoke.

Quick reference

  • skillOverrides with "name-only" keeps a low-priority skill listed without its description.
  • /doctor estimates the skill listing's context cost and its largest contributors.
  • Typing /pr-ready after other words in a message permits Claude to run it but doesn't run it directly; put the command first to run it.

Remember this

Lead with a concrete trigger in users' own words, and prune skills that are listed but never used.

Invocation control, arguments, and running in a subagent

By default both you and Claude can invoke a skill. Two fields narrow that:

  • disable-model-invocation: true for anything with side effects or timing you want to control, such as /deploy or /commit. Claude cannot run it on its own, and its description is not even listed to Claude. If Claude tries, Claude Code blocks the call and tells it not to reproduce the steps another way.
  • user-invocable: false for background knowledge, such as how a legacy system works, which Claude should apply when relevant but which isn't a command anyone would type.

Arguments arrive through $ARGUMENTS, or by position with $0, $1, or by name with an arguments list:

1---2description: Fix a GitHub issue by number3disable-model-invocation: true4arguments: [issue]5argument-hint: [issue-number]6---7 8Fix GitHub issue $issue. Read the issue with `gh issue view $issue`, write a failing test first, then fix it.

context: fork runs the skill as a subagent of the type named in agent. The subagent does not see your conversation, so the skill must contain a complete task; a skill of guidelines with no task returns nothing useful. Forked skills run in the background by default (set background: false to wait), and a background fork's edits are outside your session's checkpoints, so /rewind will not undo them; use git.

Who can invoke a skill: the default, disable-model-invocation, and user-invocable false
Who can invoke a skill: the default, disable-model-invocation, and user-invocable false

Quick reference

  • The built-in Explore and Plan agents start without your CLAUDE.md, so a forked skill using them must carry all the context it needs.
  • Multi-word arguments need quotes: /my-skill "hello world" second makes $0 = hello world.
  • If an argument is passed but no placeholder uses it, Claude Code appends ARGUMENTS: <value> to the skill content.

Remember this

Gate side-effecting skills to manual use, hide pure background knowledge from the menu, and fork only skills that contain a complete task.

Injected context: power and failure modes

The !command` syntax (or a fenced block opened with three backticks and ! for several lines) runs shell commands **before** Claude reads the skill and inlines their output. That is what makes pr-ready` accurate. It also has sharp edges worth knowing before you rely on it:

  • A failed command aborts the whole invocation. Claude never sees the skill and you get Shell command failed for pattern "...". Any non-zero exit counts, except exit code 1 from search and comparison commands such as grep and diff. Append || true to a check you expect to fail sometimes.
  • Permissions still apply, without prompts. Injected commands are checked against your permission rules; outside auto mode, anything that isn't allowed aborts the invocation. Pre-approve the commands with allowed-tools.
  • They run in the session's current directory (which moves when Claude runs cd), with stderr merged into the output and the Bash tool's two-minute timeout. Use ${CLAUDE_SKILL_DIR} or ${CLAUDE_PROJECT_DIR} for paths.
  • Output is inserted once and not re-scanned, so a command cannot emit another placeholder.

Organisations can turn injection off for user, project, and plugin skills with "disableSkillShellExecution": true, and Claude Code never runs injected commands from skills synced from claude.ai.

Quick reference

  • Keep injected commands fast and read-only; slow ones delay every invocation.
  • Large output arrives as a file path plus preview rather than inline text, so summarise with --stat or head where you can.
  • For a rule that must hold on every edit, define a hook in the skill's hooks frontmatter rather than relying on the instructions.

Remember this

Injected commands ground a skill in live data, but a single failing or unapproved command aborts the invocation; make them fast, read-only, and pre-approved.

Evaluate it: does the skill actually help?

Seeing a skill trigger tells you Claude found it, not that it did what you intended. The documentation recommends measuring two things separately: whether Claude invokes it on the prompts it should (and not on others), and whether the output is right when it does.

The method is a baseline comparison. Write five to ten realistic prompts, including some that should not trigger the skill. Run each in a fresh session with the skill on, then again with it off, and compare. A fresh session matters: context left over from writing the skill hides gaps in its instructions. To turn a project or personal skill off, set it in settings:

1{2  "skillOverrides": {3    "pr-ready": "off"4  }5}

Two tools automate the loop. The skill-creator plugin (/plugin install skill-creator@claude-plugins-official) stores test cases in evals/evals.json, runs each in an isolated subagent, grades assertions, benchmarks pass rate, time, and tokens with and without the skill, and can tune the description against should-trigger and should-not-trigger prompts. For skills that ship in a plugin, claude plugin eval runs suites with and without the plugin and exits non-zero below a threshold, so CI can gate on it.

Evaluating a skill: run realistic prompts in fresh sessions with the skill on and off, check triggering and output, then edit and rerun
Evaluating a skill: run realistic prompts in fresh sessions with the skill on and off, check triggering and output, then edit and rerun

Quick reference

  • If the skill rarely changes the outcome, delete it; every listed skill costs context on every turn.
  • If Claude follows the skill at first and drifts later, word guidance as standing instructions ("run tests after every edit") because the file is not re-read.
  • After compaction only the start of each invoked skill is kept (up to 5,000 tokens each), so put critical rules at the top.

Remember this

Compare with-skill and without-skill runs in fresh sessions, on prompts that should and shouldn't trigger it, before trusting a skill.

Hands-on: ship pr-ready in your repository

1. Create .claude/skills/pr-ready/ with the three files above, adapted to your lint and test commands. 2. Start a fresh session on a branch with real changes and ask "Is this ready for review?" Confirm the skill triggers without typing its name. 3. Break a test and run /pr-ready; confirm the failure appears in the report. 4. Rename the default branch in the injected command to one that doesn't exist and observe the abort message; restore it. 5. Run three prompts with the skill on and off (via skillOverrides) and note the difference in output quality and tokens.

Pass criterion: the skill triggers on natural phrasing, reports failing checks correctly, never prompts for the bundled script, and measurably improves on the no-skill run; otherwise rewrite the description or the steps.

Quick reference

  • Commit the skill so teammates get it, and ask one of them to run step 2 cold.
  • Keep SKILL.md short; move team review rules into checklist.md.
  • Revisit the checklist when reviewers keep flagging something the skill missed.

Remember this

Build, trigger naturally, break it on purpose, and compare against no skill before sharing it with the team.

Key takeaway

A skill is a SKILL.md with a description Claude matches against and instructions it follows, plus any supporting files and scripts. Only the description costs context until the skill is used, which is why procedures and long reference material belong in skills rather than CLAUDE.md. The pieces that make a skill reliable are a description that leads with a concrete trigger, injected commands that bring in live data (fast, read-only, and pre-approved, because a failure aborts the invocation), invocation control for anything with side effects, and a short body with the critical rules first.

Then measure it: run realistic prompts in fresh sessions with and without the skill, and keep it only if it helps. When a rule must hold every time, use a hook, not a skill instruction; see Claude Code Hooks Guide. For packaging skills with agents and hooks to share across repositories, see Claude Code MCP and Plugins.

Practice (60 min): ship pr-ready with the five steps above.

Share:

References

  1. Claude Code docs: Extend Claude with skills — checked Oct 18, 2026
  2. Agent Skills open standard — checked Oct 18, 2026
  3. Claude Code docs: Best practices — checked Oct 18, 2026
  4. anthropics/claude-plugins-official: skill-creator plugin — checked Oct 18, 2026
SH

Shoaib Hossain

Lead Author & Systems Architect

Software engineer and distributed systems architect specializing in backend scalability, cloud-native infrastructure, databases, and AI engineering workflows. Author and maintainer of Core Concept Learning.

Human-Engineered & Fact-CheckedOriginal Visual DiagramsEditorial Standards →Send Feedback

Related Articles

Plan mode is the Claude Code permission mode for thinking before touching code. Claude reads files, runs commands to exp…

Read

Claude Code is an agent that works in your project: it reads files, edits them, and runs commands such as your test suit…

Read

Running several Claude Code sessions in separate terminals works until you lose track of which one is waiting for you. A…

Read

Explore this topic

Keep learning

Follow a structured path or browse all courses to go deeper.