Adding your own custom skills to Claude Code

In an earlier post I described using an agent to manage this site as an ongoing project. This is the natural next step: instead of re-explaining the same procedure to the agent every time, I can package that procedure once as a skill and let the agent pull it in when it is relevant. This is a reference note to myself for when I build the next one.

Before writing any files, it helps to be precise about what a "skill" actually is, because the word gets used loosely. A skill is not something you add to a raw model call. To see why, it is worth looking at what a plain call to a language model even consists of.

What is actually in a model call

A single call to a language model is simple. You send a prompt, and you get text back. Optionally, you also pass a list of tools: named functions the model is allowed to call, each with a description and a schema for its arguments. If the model decides a tool is useful, it emits a structured request to call it, your program runs the function, and you feed the result back in.

That is the whole contract: prompt in, text or tool-calls out. There is no field in that request called "skills." So when I say I want to "add a custom skill," I am not modifying the model call at all. I am adding something at the layer above it, the layer Claude Code runs.

What a skill is

A skill is a folder with a SKILL.md file in it. The file has two parts: YAML frontmatter that describes the skill, and a markdown body containing the instructions the agent should follow when the skill runs.

---
name: summarize-changes
description: Summarize uncommitted changes and flag anything risky. Use when I ask what changed or want a commit message.
---

Summarize the current diff in two or three bullet points, then list
any risks such as missing error handling or hardcoded values.

The important detail is when each part is loaded. Claude Code reads the frontmatter of every installed skill at startup and keeps only the name and description in context, roughly a hundred tokens per skill. The body is not loaded until the skill is actually triggered. That mechanism is called progressive disclosure, and it is the whole reason skills scale: I can install dozens of them and the agent only pays the full token cost of the ones it uses. The description is what the agent matches against my request to decide whether a skill is relevant, so it has to say both what the skill does and when to use it.

A skill runs one of two ways. The agent can load it automatically when my request matches the description, or I can invoke it directly by typing a slash command named after the folder, so a folder named new-article becomes /new-article.

How a skill differs from a tool

This is the distinction I keep wanting to blur, so I am writing it down plainly.

A tool is a capability. It is a thing the model can do that it otherwise could not: read a file, run a shell command, query an API. Tools live in the model call itself, as function definitions the model can invoke.

A skill is know-how. It is a reusable set of instructions for a recurring task, packaged so the agent can pull it in when relevant. A skill does not give the agent any new capability. It shapes how the agent uses the capabilities it already has.

Put simply: tools expand what the agent can do; skills encode how I want a particular job done. A skill will usually tell the agent to use several tools in a specific order, but the skill itself is text, not a function.

Why bother

The trigger for writing a skill is repetition. If I find myself pasting the same checklist or the same multi-step procedure into the chat again and again, that is a skill waiting to happen. Three concrete payoffs:

  • Consistency. The procedure runs the same way every time instead of depending on how I happened to phrase it.
  • Less repetition. I write the steps once and invoke them with a few keystrokes.
  • Cheap to keep around. Because of progressive disclosure, an unused skill costs almost nothing, so there is no penalty for having a large library of them.

Where skills live

There are two places I care about:

  • Personal, at ~/.claude/skills/<skill-name>/SKILL.md. Available in all my projects. Good for general habits like summarizing a diff.
  • Project, at .claude/skills/<skill-name>/SKILL.md, committed to the repo. Only available in that project, and shared with anyone who clones it. Good for procedures that only make sense for one codebase.

The example below is project-level, because scaffolding an article only makes sense inside this site.

A worked example: a /new-article skill

Every post on this site is an MDX file at src/app/articles/<slug>/page.mdx that exports an article metadata object and wraps its content in ArticleLayout. Creating one by hand means copying an existing post and clearing it out. That is exactly the kind of boring, repeatable procedure a skill should own.

The skill is a folder with two files. The SKILL.md holds the instructions, and a separate template.mdx holds the boilerplate the skill fills in, which keeps the instructions short and demonstrates that a skill can bundle supporting files:

.claude/skills/new-article/
├── SKILL.md
└── template.mdx

Here is SKILL.md. Note disable-model-invocation: true: creating files is a deliberate act I want to trigger myself with /new-article, not something the agent should decide to do on its own. The $0 and $1 placeholders are filled from the arguments I pass after the command.

---
name: new-article
description: Scaffold a new MDX article for this site. Use when I want to start a new blog post.
disable-model-invocation: true
argument-hint: [slug] "Article title"
---

Scaffold a new article for this site.

- Slug: `$0`
- Title: `$1`

Steps:

1. Copy `${CLAUDE_SKILL_DIR}/template.mdx` to `src/app/articles/$0/page.mdx`,
   creating the folder if it does not exist.
2. In the new file, set `date` to today's date in YYYY-MM-DD form, set
   `title` to `$1`, and write a one-sentence `description`.
3. Leave the body as a single placeholder paragraph for me to fill in.
4. Run `npm run build` to confirm the new route compiles.

And template.mdx, the boilerplate every post starts from:

import { ArticleLayout } from '@/components/ArticleLayout'

export const article = {
  author: 'Blace Houle',
  date: 'YYYY-MM-DD',
  title: 'Title goes here',
  description: 'One sentence describing the article.',
}

export const metadata = {
  title: article.title,
  description: article.description,
}

export default (props) => <ArticleLayout article={article} {...props} />

Write the article here.

With those two files in place, starting a post becomes one line:

/new-article building-a-data-pipeline "Building a data pipeline"

The agent copies the template into the right folder, fills in the date and title, and runs the build. I am left with a compiling page and an empty body to write into, which is the only part that actually needs me.

Going further

The SKILL.md body can also run shell commands before the agent sees it, using a backtick command prefixed with an exclamation mark. Claude Code runs the command first and drops its output into the instructions. A skill that reviews my changes could start with the live diff already inlined:

## Current changes

!`git diff HEAD`

## Instructions

Summarize the changes above and flag anything risky.

That keeps the skill grounded in the real state of the repo instead of whatever the agent guessed from open files.

When not to reach for a skill

A skill is not always the right tool. I skip it when:

  • The task is genuinely one-off. Writing a skill for something I will do once is just overhead.
  • The thing I actually need is a new capability, not a procedure. That is a tool or an MCP server, not a skill.
  • The instruction is a stable fact about the project rather than a procedure. That belongs in CLAUDE.md, which is always loaded, rather than in a skill that loads on demand.

The test I use is simple: if I have explained the same sequence of steps more than twice, it should be a skill. This post is a small example of the payoff, since a /new-article skill would have scaffolded the very page you are reading.