EngineeringAI

How to Build Your Own Claude Code Skills - The Complete Guide

A practical, in-depth guide to building custom Claude Code skills. Covers SKILL.md format, frontmatter, triggers, arguments, subagents, dynamic context injection, and real-world examples.

Isaac··9 min read

If you use Claude Code regularly, you have probably built up a set of repeatable workflows. Maybe you write commit messages the same way every time. Maybe you have a specific process for reviewing pull requests, deploying to staging, or researching a new library before adopting it.

Every time you explain that process to Claude from scratch, you are wasting context and time. Skills solve this. They are reusable instruction files that teach Claude how to execute your workflows — consistently, every session, without you repeating yourself.

This guide covers everything you need to build your own Claude Code skills, from the basics to advanced patterns like subagent execution and dynamic context injection. If you have built slash commands before, skills are the evolution of that concept — and they are significantly more powerful.

What Is a Claude Code Skill?

A skill is a markdown file called SKILL.md that lives in a specific directory. It contains two things: YAML frontmatter that tells Claude when and how to use the skill, and markdown instructions that Claude follows when the skill is invoked.

Skills are not plugins. They are not extensions with runtime capabilities. They are instruction files. They cannot run code independently — they instruct Claude to use its existing tools (Read, Write, Bash, Grep, and so on) in a specific way, following a specific workflow.

Custom commands (.claude/commands/*.md) and skills (.claude/skills/*/SKILL.md) have been merged. Both create the same /slash-command interface. Your existing command files keep working, but skills add supporting files, frontmatter control, and auto-discovery.

The Minimal Skill

Here is the absolute minimum you need:

Directory structure
my-skill/
└── SKILL.md
text
SKILL.md
---
name: my-skill
description: What this skill does and when to use it.
---

# My Skill

Instructions for Claude go here.
yaml

The name field becomes the /slash-command. The description helps Claude decide when to load the skill automatically. The markdown body is what Claude actually reads and follows.

Where Skills Live

Where you store a skill determines who can use it. There are three main locations:

ScopePathWho Gets It
Personal~/.claude/skills/<skill-name>/SKILL.mdYou, across all projects
Project.claude/skills/<skill-name>/SKILL.mdAnyone who clones the repo
Plugin<plugin>/skills/<skill-name>/SKILL.mdWhere the plugin is enabled

Personal skills are great for your own workflows — commit messages, code review checklists, research processes. Project skills are great for team conventions — deploy procedures, PR templates, architecture guidelines. Commit your .claude/skills/ directory to version control and your entire team gets the same workflows.

When skills share the same name across levels, higher-priority locations win: enterprise > personal > project. Plugin skills use a namespace (plugin-name:skill-name), so they never conflict.

Monorepo Support

Claude Code automatically discovers skills from nested .claude/skills/ directories. If you are editing a file in packages/frontend/, Claude also looks for skills in packages/frontend/.claude/skills/. This means each package in a monorepo can have its own skills.

The Frontmatter — Configuring Skill Behaviour

The YAML frontmatter between the --- markers controls how your skill behaves. Every field is optional except description, which is strongly recommended.

Here is a reference of every available field:

FieldWhat It Does
nameDisplay name and /slash-command. Defaults to directory name. Lowercase, hyphens, max 64 chars.
descriptionWhat the skill does and when to use it. Claude uses this to decide when to auto-load. Max 250 chars shown in listing.
argument-hintHint shown during autocomplete. Example: [issue-number] or [filename] [format].
disable-model-invocationSet true to prevent Claude from auto-loading. User must invoke manually with /name.
user-invocableSet false to hide from the / menu. Only Claude can invoke it.
allowed-toolsRestrict which tools Claude can use when the skill is active.
modelOverride the model used when the skill runs.
effortOverride effort level: low, medium, high, or max.
contextSet to fork to run in an isolated subagent context.
agentWhich subagent type to use with context: fork (Explore, Plan, general-purpose, or custom).
pathsGlob patterns that limit when the skill auto-activates based on which files you are working with.
hooksHooks scoped to this skill lifecycle.
shellShell for inline commands: bash (default) or powershell.

Controlling Who Can Invoke a Skill

By default, both you and Claude can invoke any skill. You type /skill-name, or Claude loads it automatically when relevant. Two frontmatter fields change this:

disable-model-invocation: true — Only you can invoke the skill. Use this for workflows with side effects: deployments, sending messages, deleting things. You do not want Claude deciding to deploy because your code looks ready.

user-invocable: false — Only Claude can invoke the skill. Use this for background knowledge that is not actionable as a command. A legacy-system-context skill explains how an old system works. Claude should know this when relevant, but /legacy-system-context is not a meaningful action for users.

Here is how these settings affect behaviour:

SettingYou Can InvokeClaude Can InvokeDescription in Context
DefaultYesYesAlways loaded
disable-model-invocation: trueYesNoNot loaded — hidden from Claude
user-invocable: falseNoYesAlways loaded

Writing Good Descriptions

The description is the single most important field. It determines whether Claude loads your skill at the right time. A vague description means your skill never triggers. An overly broad description means it triggers when you do not want it.

Weak vs Strong Descriptions

Here is a weak description:

description: Generates commit messages.
yaml

Here is a strong description:

description: Generates structured commit messages following the Conventional
  Commits standard. Use when you want to commit your changes and need a
  well-formatted message. Triggers on "write a commit message", "commit my
  changes", "summarize my staged diff", "what should my commit say", or any
  request to describe staged changes for version control.
yaml

The strong version does three things the weak one does not: it specifies the output format (Conventional Commits), it describes the use case (committing changes), and it lists explicit trigger phrases. If your skill is not triggering when you expect, the fix is almost always adding more trigger phrases to the description.

Descriptions longer than 250 characters get truncated in the skill listing. Front-load the key use case in the first sentence.

Writing Effective Instructions

The markdown body after the frontmatter is what Claude actually follows. Good instructions share a few principles.

Principle 1: Generate First, Clarify Second

Skills should produce output immediately. Make assumptions and flag them rather than asking questions. If Claude asks "what format do you want?" every time you invoke a commit message skill, the skill is not doing its job.

Principle 2: Define the Output Format Explicitly

Specify exact structure, required fields, and character limits. If you want a commit message in Conventional Commits format, show Claude exactly what that looks like — do not just say "write a good commit message."

Principle 3: Include Counterexamples

Show Claude what not to do. This is especially effective when output format drifts over time:

## Common mistakes to avoid

Wrong: "Updated the authentication flow"
Right: "refactor(auth): simplify token validation logic"

Wrong: "Fixed bugs"
Right: "fix(api): handle null response from upstream service"
markdown

Principle 4: Be Directive, Not Conversational

Skills are instructions, not conversations. Use imperative verbs: "Read the staged diff", "Generate a commit message", "Run the test suite." Do not write "You might want to consider checking the staged diff." Tell Claude what to do.

Passing Arguments to Skills

Skills accept arguments via the $ARGUMENTS placeholder. When you run /fix-issue 123, $ARGUMENTS gets replaced with 123.

SKILL.md
---
name: fix-issue
description: Fix a GitHub issue by number
disable-model-invocation: true
argument-hint: <issue-number>
---

Fix GitHub issue $ARGUMENTS following our coding standards.

1. Read the issue description
2. Understand the requirements
3. Implement the fix
4. Write tests
5. Create a commit
yaml

For skills that take multiple arguments, use positional access with $ARGUMENTS[N] or the shorthand $N:

SKILL.md
---
name: migrate-component
description: Migrate a component from one framework to another
---

Migrate the $0 component from $1 to $2.
Preserve all existing behaviour and tests.
yaml

Running /migrate-component SearchBar React Vue replaces $0 with SearchBar, $1 with React, and $2 with Vue.

If your skill does not include $ARGUMENTS anywhere in the content but arguments are passed, Claude Code appends ARGUMENTS: <your input> to the end automatically. So arguments are never lost.

Full Example: Commit Message Writer

Let us build a complete, production-quality skill from scratch. This one generates commit messages following the Conventional Commits standard.

Step 1: Create the Directory

mkdir -p ~/.claude/skills/commit-message-writer
bash

On Windows PowerShell:

New-Item -ItemType Directory -Force -Path "$HOME\.claude\skills\commit-message-writer"
powershell

Step 2: Write the SKILL.md

~/.claude/skills/commit-message-writer/SKILL.md
---
name: commit-message-writer
description: Generates structured commit messages following the Conventional Commits
  standard. Use when you want to commit your changes and need a well-formatted
  message. Triggers on "write a commit message", "commit my changes", "summarize
  my staged diff", "what should my commit say", or any request to describe staged
  changes for version control.
---

# commit-message-writer

You generate structured commit messages from staged git changes.

## How to invoke

Run `git diff --staged` to read the staged changes. If nothing is staged,
tell the user and suggest they run `git add` first.

Generate first. Do not ask clarifying questions before producing the commit
message. If you need to make assumptions about scope or type, make them and
note them after the output.

## Output format

```
type(scope): short description

[body — optional, include if changes are non-trivial]

[footer — optional]
```

**Type** — choose one:
- `feat` — a new feature
- `fix` — a bug fix
- `docs` — documentation changes only
- `refactor` — code change that neither fixes a bug nor adds a feature
- `test` — adding or updating tests
- `chore` — build process, tooling, or dependency updates

**Scope** — the module, file, or area affected. Use the directory name or
component name. Omit if the change spans the entire codebase.

**Short description** — imperative mood, under 72 characters, no period.
"Add user authentication" not "Added user authentication."

**Body** — what changed and why, not how. One bullet per logical change.
Skip if the short description is self-explanatory.

**Footer** — include `BREAKING CHANGE:` if backward compatibility breaks.
Include `Closes #N` if it resolves a GitHub issue.

## Quality rules

- Never use "updated", "changed", or "modified" — be specific
- Never write "various improvements" or "misc fixes" — name what improved
- If more than three files changed across unrelated concerns, flag it:
  "These changes may be better split into separate commits"
- Short description must be under 72 characters — count before outputting

## Example output

Input: staged changes adding a rate limiter to an API endpoint

```
feat(api): add rate limiting to /query endpoint

- Limits requests to 100 per minute per IP using token bucket algorithm
- Returns 429 with Retry-After header when limit exceeded
- Adds rate limit configuration to environment variables

Closes #47
```
yaml

Step 3: Test It

Open Claude Code in any git repository with staged changes. Test it three ways:

  1. Direct invocation: type /commit-message-writer
  2. Natural language: "write a commit message for my staged changes"
  3. Edge case: stage nothing and verify the skill tells you to run git add first

Adding Supporting Files

Skills can include multiple files in their directory. This keeps SKILL.md focused while giving Claude access to detailed reference material when needed.

my-skill/
├── SKILL.md           # Main instructions (required)
├── reference.md       # Detailed API docs
├── examples/
│   └── sample.md      # Example outputs
└── scripts/
    └── validate.sh    # Scripts Claude can execute
text

Reference supporting files from your SKILL.md so Claude knows they exist:

## Additional resources

- For complete API details, see [reference.md](reference.md)
- For usage examples, see [examples.md](examples.md)
markdown

Keep SKILL.md under 500 lines. Move detailed reference material, long examples, and API documentation to separate files. Claude loads them only when needed, keeping context usage efficient.

Advanced: Dynamic Context Injection

This is one of the most powerful skill features. The !`command` syntax runs shell commands before Claude sees the skill content. The command output replaces the placeholder, so Claude receives real data — not a template.

SKILL.md
---
name: pr-summary
description: Summarise changes in a pull request
context: fork
agent: Explore
allowed-tools: Bash(gh *)
---

## Pull request context
- PR diff: !`gh pr diff`
- PR comments: !`gh pr view --comments`
- Changed files: !`gh pr diff --name-only`

## Your task
Summarise this pull request. Focus on what changed and why.
Highlight any breaking changes or areas that need careful review.
yaml

When this skill runs, each !`command` executes immediately — before Claude processes anything. Claude only sees the fully rendered prompt with actual PR data baked in. This is preprocessing, not tool invocation.

This pattern is incredibly useful for skills that need live data: git status, API responses, environment information, CI pipeline results.

Advanced: Running Skills in Subagents

Add context: fork to your frontmatter when you want a skill to run in isolation. The skill content becomes the prompt that drives a subagent. It will not have access to your conversation history — it runs in its own context window.

SKILL.md
---
name: deep-research
description: Research a topic thoroughly across the codebase
context: fork
agent: Explore
---

Research $ARGUMENTS thoroughly:

1. Find relevant files using Glob and Grep
2. Read and analyse the code
3. Summarise findings with specific file references
4. Identify potential issues or improvements
yaml

When this skill runs:

  1. A new isolated context is created
  2. The subagent receives the skill content as its task
  3. The agent field determines the execution environment (model, tools, permissions)
  4. Results are summarised and returned to your main conversation

The agent field accepts built-in types (Explore, Plan, general-purpose) or any custom subagent you have defined in .claude/agents/. If omitted, it defaults to general-purpose.

context: fork only makes sense for skills with explicit task instructions. If your skill just contains guidelines like "use these API conventions" without an actionable task, the subagent receives the guidelines but has nothing to do — and returns without meaningful output.

Advanced: Restricting Tool Access

Use allowed-tools to limit what Claude can do when a skill is active. This is useful for safety — creating read-only exploration skills, or preventing a research skill from modifying files:

SKILL.md
---
name: safe-reader
description: Explore the codebase without making changes
allowed-tools: Read, Grep, Glob
---

Explore the codebase and answer the user's question.
Do not modify any files.
yaml

Advanced: Generating Visual Output

Skills can bundle and run scripts in any language. One powerful pattern is generating interactive HTML files that open in your browser — codebase visualisations, dependency graphs, test coverage reports.

The pattern is simple: your SKILL.md tells Claude to run a script bundled in the skill directory. The script does the heavy lifting. Claude handles orchestration.

codebase-visualizer/
├── SKILL.md
└── scripts/
    └── visualize.py
text
SKILL.md
---
name: codebase-visualizer
description: Generate an interactive collapsible tree visualisation of your
  codebase. Use when exploring a new repo or understanding project structure.
allowed-tools: Bash(python *)
---

# Codebase Visualizer

Run the visualisation script from your project root:

```bash
python ~/.claude/skills/codebase-visualizer/scripts/visualize.py .
```

This creates codebase-map.html and opens it in your default browser.
yaml

The script can be written in Python, Node, Bash — whatever makes sense. Because it is bundled with the skill, it is portable and self-contained. You can use the $CLAUDE_SKILL_DIR variable to reference the script location regardless of the current working directory.

String Substitutions Reference

Skills support several dynamic variables that get replaced at runtime:

VariableWhat It Does
$ARGUMENTSAll arguments passed when invoking the skill
$ARGUMENTS[N] or $NAccess a specific argument by position (0-based)
${CLAUDE_SESSION_ID}Current session ID — useful for logging or session-specific files
${CLAUDE_SKILL_DIR}Directory containing the SKILL.md file — use to reference bundled scripts

Cross-Platform Compatibility

Claude Code skills follow the Agent Skills open standard. The same SKILL.md format works across multiple AI tools:

AgentSkills Directory
Claude Code~/.claude/skills/
GitHub Copilot~/.copilot/skills/ or .github/skills/
Cursor~/.cursor/skills/
Gemini CLI~/.gemini/skills/

Write once, use across tools. The frontmatter format and markdown instructions are identical. Claude Code adds extra features like subagent execution and dynamic context injection, but the core standard is portable.

Choosing What to Build

The best skills share three properties:

  1. Repeatable workflow — you follow the same steps every time, even if the specifics vary
  2. Clear trigger — you can complete the sentence "I need this skill when I want to..."
  3. Consistent output format — produces results with a fixed structure (commit messages, PR descriptions, code reviews)

Good candidates for skills:

  • Commit messages — same format every time
  • Pull request descriptions — consistent structure with summary, test plan, breaking changes
  • Code review checklists — your team's standard review process encoded
  • Changelog entries — structured output for version documentation
  • Deploy workflows — step-by-step with safety checks
  • Research workflows — fetch docs, explore codebase, write findings

Poor candidates:

  • Open-ended requests like "help me think through this architecture"
  • One-off tasks you will never repeat
  • Tasks that require so much context they cannot be templated

Troubleshooting

Skill Not Triggering

  • Check the description includes keywords users would naturally say
  • Verify the skill appears when you ask "What skills are available?"
  • Add more explicit trigger phrases to the description
  • Try invoking directly with /skill-name to confirm it works at all

Skill Triggers Too Often

  • Make the description more specific and narrow
  • Add disable-model-invocation: true if you only want manual invocation

Descriptions Getting Cut Short

If you have many skills, descriptions are shortened to fit a character budget (1% of context window, minimum 8,000 characters). Front-load the key use case in the first sentence. You can raise the limit by setting the SLASH_COMMAND_TOOL_CHAR_BUDGET environment variable.

Improving Skills Over Time

Skills are not write-once-forget. They improve with use.

  • When the skill does not trigger: add the phrase you used to the description
  • When the output format drifts: add more explicit format rules and counterexamples
  • When scope creeps: resist adding unrelated tasks — build a separate skill instead
  • When context gets too large: move reference material to supporting files

Each skill should do one thing well. Three focused skills beat one "do everything" skill every time.

The Bottom Line

Claude Code skills are markdown files. That simplicity is the point. You do not need to learn a framework, install dependencies, or write runtime code. You write instructions in a SKILL.md file, put it in the right directory, and Claude follows them.

Start with one skill that automates your most repeated workflow. Get the description right so it triggers reliably. Add counterexamples when the output drifts. Then build the next one.

The compound effect is real. A developer with ten well-tuned skills is not just faster — they are more consistent. Every commit message follows the same standard. Every PR description has the same structure. Every deploy follows the same safety checks. That consistency compounds over weeks and months.

At Tally Digital, we build AI-powered tools and automations for New Zealand businesses. If you want help setting up Claude Code workflows, building custom skills for your team, or integrating AI into your development process, get in touch.

Share this article

#Claude Code#AI Agents#Developer Tools#Anthropic#Skills#Slash Commands#Automation