The Problem of Repeating Everything

Have you ever had to explain the same thing to someone twenty times? Now imagine that, but with a robot that also loses its memory every few hours.

“No, Claude, the commit has to pass tests first.” “Claude, I already told you to use the type: description format.” “Don’t add emojis, damn it!”

This was my daily routine until I discovered Skills. In plain English: they’re instructions you write once and Claude follows forever. Like training a dog, but without the treats.

What Skills Are

Since version 2.1.3, Claude Code merged the old slash commands with something more powerful: Skills. They’re Markdown files with instructions that Claude can execute in two ways:

  1. Manually: when you type /my-skill
  2. Automatically: when Claude detects it should use it

That second point is the magic. You no longer have to remember to invoke the command. If you have a skill that says “use when the user finishes a task and has uncommitted changes,” Claude will do it automatically.

It’s like having a butler who knows when to clear the table without being asked.

Where They Live

~/.claude/skills/          # Personal (all your projects)
.claude/skills/            # Project-specific (shared with team)
~/.claude/commands/        # Legacy, still works
.claude/commands/          # Legacy, still works

If you want only you to use the skill, put it in your home. If you want the whole team to have it, commit it to the repo. That simple.

Anatomy of a Skill

A skill is a Markdown file with YAML frontmatter and then the content:

1
2
3
4
5
6
7
8
---
name: my-skill
description: Brief description of what it does
---

# Instructions

What Claude should do when this skill is invoked.

That’s the minimum. But the frontmatter has quite a few more options worth knowing.

Required Fields

name

The skill identifier. Only lowercase, numbers and hyphens (max 64 characters). Must match the file or directory name.

1
2
name: check-types      # ✓ valid
name: Check_Types      # ✗ invalid (uppercase and underscore)

description

This is the most important field. Claude uses it for two things:

  1. Deciding when to auto-invoke the skill
  2. Understanding what it should do

Maximum 1024 characters. Include keywords that the user would naturally say.

1
2
3
4
5
6
7
8
# Bad - too vague
description: Does things with commits

# Good - specific and with triggers
description: >
  Creates git commits checking type-check, lint and tests.
  Use when user says "commit", "save changes", or finishes a task
  with pending changes.

Optional Fields

model

Forces a specific model for this skill. Useful for tasks requiring more capability.

1
2
3
model: opus    # For security audits, complex refactoring
model: sonnet  # Balance between capability and cost
model: haiku   # For simple and quick tasks

If not specified, uses the current conversation’s model.

allowed-tools

Restricts which tools Claude can use. Critical for read-only or secure skills.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Can only read, not modify
allowed-tools:
  - Read
  - Grep
  - Glob

# Can only run specific commands
allowed-tools:
  - Bash(git:*)      # Only git commands
  - Bash(uv:*)       # Only uv commands
  - Read

Practical example: an analysis skill that should NOT touch anything:

1
2
3
4
5
6
7
8
---
name: analyze-deps
description: Analyzes project dependencies without modifying anything
allowed-tools:
  - Read
  - Grep
  - Bash(uv pip list:*)
---

context: fork

Runs the skill in an isolated sub-agent with its own context. The main conversation history doesn’t get contaminated.

1
context: fork

Useful for complex multi-step operations where you don’t want to fill the chat with noise.

agent

Only works with context: fork. Defines what type of agent executes the skill.

1
2
3
context: fork
agent: Explore    # Quick exploration agent
agent: Plan       # Planning agent

user-invocable

Controls whether it appears in the / (slash commands) menu. Defaults to true.

1
user-invocable: false  # Hidden from menu, but Claude can use it

Useful for internal skills that should only trigger automatically.

disable-model-invocation

Blocks Claude from invoking the skill on its own. Only you can activate it with /name.

1
disable-model-invocation: true

Useful for destructive or expensive operations that require explicit human decision.

hooks

Defines hooks that run during the skill lifecycle. Supports PreToolUse, PostToolUse and Stop.

1
2
3
4
5
6
7
hooks:
  PreToolUse:
    - matcher: "Bash"
      hooks:
        - type: command
          command: "./scripts/validate-input.sh $TOOL_INPUT"
          once: true

Substitution Variables

Within the skill content you can use:

VariableContains
$ARGUMENTSArguments passed when invoking /skill arg1 arg2
${CLAUDE_SESSION_ID}Current session ID (useful for logs)

Summary Table

FieldRequiredPurpose
nameSkill identifier
descriptionWhen and what to use it for
modelForce specific model
allowed-toolsRestrict tools
contextfork for isolated sub-agent
agentAgent type (with context: fork)
user-invocableShow/hide in / menu
disable-model-invocationBlock auto-invocation
hooksLifecycle hooks

Complete Example

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
---
name: security-audit
description: >
  OWASP security audit. Use when user asks to review
  security, look for vulnerabilities, or before deploying to production.
model: opus
allowed-tools:
  - Read
  - Grep
  - Glob
user-invocable: true
disable-model-invocation: true  # Manual only, it's expensive
---

# Security Audit

[instructions...]

For the complete reference, check the official Agent Skills documentation.

Free Text or Deterministic Code?

This is the million-dollar question: if skills are Markdown, does it mean Claude always “interprets” what you write? Can I do something truly predictable?

The short answer: skills are as deterministic as you write them.

Think of a spectrum:

Vague/Flexible ──────────────────────────► Deterministic
"review the code"        "execute these 3 commands in order"

Flexible skill (Claude decides)

1
2
3
4
5
6
---
name: review
description: Reviews code looking for problems
---

Analyze the code and suggest improvements.

Here Claude has total freedom. Can look at whatever it wants, suggest whatever seems right. Useful for exploration, dangerous for critical processes.

Deterministic skill (disguised script)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
---
name: check
description: Mandatory quality checks
allowed-tools:
  - Bash
---

Execute **exactly** these commands in order:

1. `uv run basedpyright src/`
2. `uv run ruff check src/`
3. `uv run pytest -x`

## Rules

- **DO NOT interpret** errors creatively
- **DO NOT continue** if any fails
- **DO NOT suggest** automatic fixes
- Report only: ✓ passed / ✗ failed with output

This is basically a 3-line script. Claude has no room to be creative. Execute, report, period.

Skill with conditional logic

1
2
3
4
5
6
7
8
9
---
name: release
description: Prepares project release
---

## Step 1: Verify branch

```bash
git branch --show-current
  • If NOT mainABORT with “Only from main”

Step 2: Clean state

1
git status --porcelain
  • If there’s output → ABORT with “Uncommitted changes”

Step 3: Bump + push

1
2
uv run bump2version patch
git push && git push --tags

There's branch logic here, but it's still deterministic: the conditions are explicit.

### Skill that invokes a real script

If you need truly complex logic (loops, parsing, APIs), put the code in a script and have the skill just execute it:

.claude/skills/deploy/ ├── SKILL.md └── deploy.sh


**SKILL.md:**
```markdown
---
name: deploy
description: Deploys to production
---

Execute:

```bash
bash .claude/skills/deploy/deploy.sh

Report the result. DO NOT modify the script.


**deploy.sh:**
```bash
#!/bin/bash
set -e
uv run pytest || exit 1
hugo --minify
rsync -avz public/ user@server:/var/www/

Best of both worlds: complex logic lives in Bash/Python where it belongs, and the skill is just the trigger.

When to use each approach

NeedApproach
Fixed commands, always the sameDeterministic skill
Complex logic with many branchesExternal script
Analysis requiring judgmentFlexible skill with guardrails
Dangerous operationsRestrictive allowed-tools

Real Example: the commit skill

This is the one I use most. Before I had to remember: “okay, run tests, then linter, then type-check, and only then commit”. Now I simply say “commit” and Claude does everything automatically.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
---
name: commit
description: Creates git commits with mandatory quality verification.
  Runs type-check, lint and tests before committing.
---

# Commit

## When to Use (Automatic)

Apply when:
- User says "commit", "save changes"
- User finishes a task and has uncommitted changes

## Forbidden

- Committing without running verifications
- Asking for confirmation (just do it)
- Adding Co-Authored-By
- Using emojis in commit messages

## Process

### Phase 1: Detect changes

```bash
git diff --name-only HEAD

Phase 2: MANDATORY verifications

1
2
3
uv run basedpyright src/
uv run ruff check src/
uv run pytest

If any fails, DO NOT continue.

Phase 3: Create commit

  1. git add -A
  2. Analyze changes
  3. Generate message: type: description
  4. git commit

See the "When to Use" section? That's what enables auto-invocation. Claude reads that and thinks: "ah, the user just said 'done', there are pending changes, I should use this skill".

## Another example: task archiving

If you use a `TASKS.md` file to track what you do (I did before Beads), this skill automatically cleans completed tasks:

```markdown
---
name: archive-tasks
description: Archives completed tasks from TASKS.md to TASKS-DONE.md.
  Use automatically when TASKS.md has many completed tasks
  or exceeds 20K tokens.
---

# Archive Tasks

## When to Use (Automatic)

- TASKS.md has more than 50 completed tasks `[x]`
- TASKS.md exceeds 20,000 tokens
- User mentions TASKS.md is too big

## Process

1. Read `docs/llm/TASKS.md`
2. Identify completed tasks (`[x]`)
3. Move to `docs/llm/TASKS-DONE.md` with date
4. Remove from TASKS.md
5. Report how many were archived

## Rules

- **DO NOT delete** pending tasks `[ ]`
- **PRESERVE** context (parent section)
- **ADD** archival date

The beauty is you don’t have to remember. Claude sees that TASKS.md is huge and acts.

Tips for Writing Good Skills

1. Specific descriptions

1
2
3
4
5
6
# Bad
description: Does things with commits

# Good
description: Creates git commits checking type-check, lint and tests.
  Blocks if there are errors.

2. Define when to apply

1
2
3
4
5
6
## When to Use This Skill (Automatic)

Apply automatically when:
- User says "commit" or "save changes"
- There are staged changes ready
- User finishes a task

3. Be explicit about prohibitions

Claude tends to want to be polite and ask for confirmation. If you don’t want that, say so clearly:

1
2
3
4
5
## Forbidden

- Asking for confirmation (NEVER)
- Adding Co-Authored-By
- Using emojis

4. Use model: opus for important things

If the skill does something critical (security audit, complex refactoring), force the most capable model:

1
2
3
4
5
---
name: owasp
description: OWASP security audit
model: opus
---

5. Restrict tools if needed

Sometimes you want a skill that only reads, without modifying anything:

1
2
3
4
5
6
7
8
---
name: readonly-analysis
description: Analyzes code without modifying it
allowed-tools:
  - Glob
  - Grep
  - Read
---

Simple vs Complex Skills

A simple skill is a single file:

.claude/skills/review.md

A complex skill is a directory with resources:

.claude/skills/deploy/
├── SKILL.md                 # Instructions
├── templates/
│   └── k8s-deployment.yaml
└── scripts/
    └── healthcheck.sh

Claude can read the directory files as additional context.

What I Use

SkillFor whatAuto-invocation
commitCommit with verificationsWhen I say “commit” or finish something
check-diagnosticsCheck types and lintBefore commits
owaspSecurity auditManual (it’s expensive)
archive-tasksClean up TASKS.mdWhen it’s too big

The Difference from Legacy Commands

AspectSkillsCommands
Auto-invocationYesNo
StructureDirectory or fileFile only
RecommendationUse for everything newLegacy

Commands still work, but skills are strictly better. If you have old commands, no need to migrate them, but for new things use skills.

Conclusion

Skills are basically programming, but in natural language. You define what you want to happen, when, and with what restrictions. Claude does the rest.

The best part is they’re versioned with your code. If you work on a team, everyone has the same skills. If you change something, it’s in git history.

Is it worth the effort to write them? If you repeat the same thing more than three times, absolutely. Every skill you write is a conversation you’ll never have to have again.

Now if you’ll excuse me, I need to go teach Claude that “refactor” doesn’t mean “rewrite everything from scratch”.


TL;DR: Skills are Markdown instructions that Claude Code executes manually or automatically. They can be as flexible or deterministic as you need: from “analyze this” to scripts disguised as prose. If you need complex logic, invoke external scripts. They live in .claude/skills/ and are versioned with your code.