Skip to content

Custom Commands and Skills

Claude Code ships with a set of built-in slash commands , /doctor, /code-review, /debug, /batch, /loop, /run, /verify , that cover common coding tasks. But the real value comes when you teach it your own procedures: the build-and-test sequence your team uses, the way you structure commit messages, the linting rules that matter to you.

Claude Code gives you two mechanisms for this: custom slash commands and skills. They overlap in purpose but differ in how they're invoked, where they live, and when Claude loads them. Understanding that difference is the first step to building a library of reusable procedures that follows you across projects.

Gnome mascot

What you'll learn

  • Custom commands live in .claude/commands/<name>.md and are invoked with /name , they're explicit, gated behind a slash
  • Skills live in .claude/skills/<name>/SKILL.md and are loaded automatically when Claude decides they're relevant, they're ambient, always watching
  • Both use the same markdown format with optional YAML frontmatter; skills additionally support ! backtick command backtick `` for dynamic context injection
  • Skills files are watched for live changes, edit a SKILL.md mid-session and Claude picks it up without restarting

The problem

You've used /code-review and /doctor and they work. But you keep typing the same preamble before every PR: "Run the tests, check the linting, if both pass then open the PR with the conventional commit title..." That's a procedure, and right now it lives in your muscle memory rather than in Claude's.

Or maybe you want Claude to always know your team's API conventions without typing them into CLAUDE.md, skills can load project-wide conventions that Claude checks on every relevant task without you asking.

The difference between a command and a skill comes down to when it fires. A command fires when you type /name. A skill fires when Claude reads its description field and decides it matches the current task. If you want Claude to follow a procedure only when you explicitly invoke it, write a command. If you want Claude to draw on a procedure automatically when the task calls for it, write a skill.

Options & when to use each

MechanismInvocationLocationBest for
Custom commandUser types /name.claude/commands/<name>.md (project) or ~/.claude/commands/<name>.md (personal)Explicit workflows you want to trigger on demand: PR creation, release tagging, deploy checklists
SkillClaude loads automatically based on description match.claude/skills/<name>/SKILL.md (project), ~/.claude/skills/<name>/SKILL.md (personal, all projects), or plugin skillsAmbient knowledge Claude should apply whenever relevant: API conventions, testing patterns, security rules
CLAUDE.mdLoaded at session start, always in contextCLAUDE.md (project root) or ~/.claude/CLAUDE.md (personal)Project-wide facts, build commands, and conventions that should be available on every turn

Commands are explicit and gated. You type /release, Claude follows the release checklist. No ambiguity about when it fires because you're the one pulling the trigger.

Skills are ambient. You ask Claude to add an endpoint, it checks the api-conventions skill and follows your team's patterns without being told. You file a bug report, it checks the debugging-checklist skill. The tradeoff is that skills burn a small amount of context window on every turn, Claude scans available skill descriptions to decide which ones are relevant, so a skill with a vague description gets loaded more often than it should, and a skill with an overly specific description never loads when you need it.

Commands and skills are not mutually exclusive. A command can reference a skill, and a skill's description can suggest invoking it with a slash command. Many bundled commands , /code-review, /debug, /batch , are implemented as skills internally.

Build it

Step 1: Write a custom command

Create .claude/commands/release.md in your project root:

markdown
---
description: "Run the release checklist: tests, lint, version bump, and tag"
---

Run the full release checklist in this order:

1. `npm test` ,  if any test fails, stop and report
2. `npm run lint` ,  if any lint error, stop and report
3. Read the current version from package.json
4. Ask me what the new version should be
5. Update package.json with the new version
6. `git add package.json && git commit -m "chore: bump version to X.Y.Z"`
7. `git tag vX.Y.Z`
8. Report: "Release vX.Y.Z is ready. Push with `git push --tags`."

Test it in an interactive session:

> /release

Claude reads the markdown file and follows the numbered steps. The YAML frontmatter is optional for commands, if you omit it, Claude treats the entire file as instructions.

Step 2: Write a skill with YAML frontmatter

Skills need the frontmatter. Create .claude/skills/api-conventions/SKILL.md:

markdown
---
description: "REST API conventions for this project: error shape, pagination format, auth headers, and endpoint naming"
---

# API Conventions

## When to apply
- The user asks to create, modify, or review an API endpoint
- The user mentions REST, HTTP, routes, controllers, or request handlers

## Error responses
Always return errors in this shape:

```json
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Human-readable description",
    "details": []
  }
}

Pagination

List endpoints use cursor-based pagination with next_cursor and has_more fields in the response body, never page and limit query params.

Auth headers

All endpoints read Authorization: Bearer <token>. No cookie-based auth. No API keys in query strings.

Endpoint naming

  • Collection: GET /api/v1/<plural-noun> (e.g. /api/v1/users)
  • Single resource: GET /api/v1/<plural-noun>/:id
  • Nested: GET /api/v1/<parent>/:id/<children>
  • Actions: POST /api/v1/<plural-noun>/:id/<verb> (e.g. /api/v1/orders/:id/cancel)

Now ask Claude to add an endpoint:

Add a GET endpoint for listing user orders, scoped to the authenticated user


Without the skill, Claude might use query-param pagination, a different error shape, or an inconsistent URL pattern. With the skill active, Claude applies your conventions automatically. You didn't type `/api-conventions` ,  the skill's `description` field matched the task and Claude loaded it on its own.

### Step 3: Use dynamic context injection

Skills support a syntax that no other Claude Code mechanism does: inline command execution with `` !`command` ``. When Claude loads a skill containing this syntax, it runs the command and injects the output as context.

```markdown
---
description: "Current git branch status and recent commits for context in code review"
---

# Git Context

Current branch and status:
!`git branch --show-current && git status --short`

Last 5 commits on this branch:
!`git log --oneline -5`

When Claude loads this skill, it runs those commands and sees the actual git state. This is useful for skills that need live information: the current branch, the list of open ports, the state of a Docker container. The command runs in the project root and times out after 5 seconds, if it hangs, Claude skips it and continues loading.

Step 4: Live skill editing

Skills directories are watched for file changes. If you edit a SKILL.md mid-session, Claude picks up the changes on the next turn without a restart. To test this:

  1. Start an interactive session
  2. Ask Claude about endpoints, it applies the current api-conventions skill
  3. In another terminal, edit .claude/skills/api-conventions/SKILL.md and change the error shape
  4. Ask Claude about endpoints again, it uses the updated shape

This makes skill development fast: edit the file, run a query, see if Claude got it right, repeat.

What goes wrong

MistakeHow you notice itThe fix
Skill description is too vague ("Coding help")Claude loads the skill on nearly every turn, burning context and degrading performanceWrite descriptions that name a specific scenario: "REST API error shape and pagination conventions" not "API stuff"
Skill description is too specificYou ask for the thing you wrote the skill for but Claude doesn't apply itTest the description against the language you actually use in prompts. If you say "add an endpoint" but the description says "creating HTTP route handlers," Claude won't match them
Command file has no YAML frontmatter but expects description behaviorThe command works when invoked with /name but never loads automaticallyCommands with no frontmatter are slash-only. Add YAML frontmatter with a description if you want it to also work as an ambient skill
!`command` hangs or times outClaude loads the skill but the injected context is empty or truncatedThe command has a 5-second timeout and output cap. Keep injected commands fast and concise. If you need a slow command's output, run it separately and paste the result
Skill and CLAUDE.md conflictClaude follows one set of instructions and ignores the other, seemingly at randomSkills take priority over CLAUDE.md when both address the same domain. If you want CLAUDE.md to be the authority on something, don't write a skill that overlaps

Confirm it worked

Create a test skill and verify Claude applies it without being told:

bash
# 1. Create a test skill
mkdir -p .claude/skills/test-convention
cat > .claude/skills/test-convention/SKILL.md << 'EOF'
---
description: "When asked to write a function, always add a docstring"
---

# Docstring Convention

## When to apply
The user asks to write or generate a function.

## Rules
Every function must include a docstring with:
- A one-line summary of what it does
- Args: parameter descriptions
- Returns: what the function returns
EOF

# 2. Test: ask Claude to write a function without mentioning docstrings
claude -p "Write a Python function called `calculate_total` that takes a list of prices and an optional tax rate and returns the total" --output-format text

# 3. Expected: the function includes a docstring matching the skill's format
# If it does, skills are loading and matching correctly

# 4. Clean up
rm -rf .claude/skills/test-convention

Then test a custom command:

bash
mkdir -p .claude/commands

cat > .claude/commands/hello.md << 'EOF'
Respond with "Hello from the custom command system!" and nothing else.
EOF

claude  # start interactive session
> /hello
# Expected: "Hello from the custom command system!"

If the command fires and the skill loads automatically, both extension mechanisms are working. You're ready to build your own library of reusable procedures.

Next: Hooks, event-driven automation that fires on tool use, session boundaries, and user prompts.