The Problem of Repetition
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 needs to pass tests first."
"Claude, I already told you to use the type: description format."
"Stop adding emojis!"
This was my daily routine until I discovered Skills. These are instructions you write once and Claude follows forever. Like training a dog, but without the treats.
What Are Skills
Since version 2.1.3, Claude Code merged the old slash commands with something more powerful: Skills. These are Markdown files with instructions that Claude can execute in two ways:
-
Manually: when you type
/my-skill - 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 directory. 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 followed by content:
---
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 many more options worth knowing.
Required Fields
name
The skill identifier. Only lowercase letters, numbers, and hyphens (max 64 characters). Must match the file or directory name.
name: check-types # ✓ valid
name: Check_Types # ✗ invalid (uppercase and underscore)
description
This is the most important field. Claude uses it for two things:
- Decide when to auto-invoke the skill
- Understand what it should do
Maximum 1024 characters. Include keywords the user would naturally say.
# Bad - too vague
description: Does things with commits
# Good - specific with triggers
description: >
Creates git commits after verifying type-check, lint, and tests.
Use when user says "commit", or finishes a task
with pending changes.
Optional Fields
model
Forces a specific model for this skill. Useful for tasks requiring more capability.
model: opus # For security audits, complex refactoring
model: sonnet # Balance between capability and cost
model: haiku # For simple, fast 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.
# Can only read, not modify
allowed-tools:
- Read
- Grep
- Glob
# Can only execute specific commands
allowed-tools:
- Bash(git:*) # Only git commands
- Bash(uv:*) # Only uv commands
- Read
Practical example: an analysis skill that must NOT touch anything:
---
name: analyze-deps
description: Analyzes project dependencies without modifying anything
allowed-tools:
- Read
- Grep
- Bash(uv pip list:*)
---
context: fork
Executes the skill in an isolated sub-agent with its own context. The main conversation history doesn't get contaminated.
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.
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.
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.
disable-model-invocation: true
Useful for destructive or expensive operations requiring explicit human decision.
hooks
Defines hooks that execute during the skill lifecycle. Supports PreToolUse, PostToolUse, and Stop.
hooks:
PreToolUse:
- matcher: "Bash"
hooks:
- type: command
command: "./scripts/validate-input.sh $TOOL_INPUT"
once: true
Substitution Variables
Within skill content you can use:
| Variable | Contains |
|---|---|
$ARGUMENTS |
Arguments passed when invoking /skill arg1 arg2
|
${CLAUDE_SESSION_ID} |
Current session ID (useful for logs) |
Summary Table
| Field | Required | Purpose |
|---|---|---|
name |
✓ | Skill identifier |
description |
✓ | When and what to use it for |
model |
Force specific model | |
allowed-tools |
Restrict tools | |
context |
fork for isolated sub-agent |
|
agent |
Agent type (with context: fork) |
|
user-invocable |
Show/hide in / menu |
|
disable-model-invocation |
Block auto-invocation | |
hooks |
Lifecycle hooks |
Complete Example
---
name: security-audit
description: >
OWASP security audit. Use when user requests security review,
vulnerability search, or before production deployment.
model: opus
allowed-tools:
- Read
- Grep
- Glob
user-invocable: true
disable-model-invocation: true # Manual only, expensive
---
# Security Audit
[instructions...]
For the complete reference, see 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 make something truly predictable?
The short answer: skills are as deterministic as you write them.
Think of it as a spectrum:
Vague/Flexible ──────────────────────────► Deterministic
"review the code" "execute these 3 commands in order"
Flexible Skill (Claude Decides)
---
name: review
description: Reviews code for issues
---
Analyze the code and suggest improvements.
Here Claude has total freedom. It can look at whatever, suggest whatever. Useful for exploration, dangerous for critical processes.
Deterministic Skill (Disguised Script)
---
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 fail
- **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, done.
Skill with Conditional Logic
---
name: release
description: Prepares project release
---
## Step 1: Verify branch
bash
git branch --show-current
- If NOT `main` → **ABORT** with "Only from main"
## Step 2: Clean state
bash
git status --porcelain
- If there's output → **ABORT** with "Uncommitted changes"
## Step 3: Bump + push
bash
uv run bump2version patch
git push && git push --tags
plaintext
Here there's branch logic, 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
markdown
SKILL.md:
---
name: deploy
description: Deploys to production
---
Execute:
bash
bash .claude/skills/deploy/deploy.sh
Report the result. **DO NOT modify the script.**
shell
deploy.sh:
#!/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
| Need | Approach |
|---|---|
| Fixed commands, always the same | Deterministic skill |
| Complex logic with many branches | External script |
| Analysis requiring judgment | Flexible skill with guardrails |
| Dangerous operations | Restrictive 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.
---
name: commit
description: Creates git commits with mandatory quality verification.
Executes type-check, lint, and tests before committing.
---
# Commit
## When to Use (Automatic)
Apply when:
- User says "commit", "save changes"
- User finishes a task and there are uncommitted changes
## Prohibited
- Committing without running checks
- 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 checks
bash
uv run basedpyright src/
uv run ruff check src/
uv run pytest
**If any fail, DO NOT continue.**
### Phase 3: Create commit
1. `git add -A`
2. Analyze changes
3. Generate message: `type: description`
4. `git commit`
markdown
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 this before Beads), this skill automatically cleans up completed tasks:
---
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 large
## 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** archive 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
# Bad
description: Does things with commits
# Good
description: Creates git commits after verifying type-check, lint, and tests.
Blocks if there are errors.
2. Define When to Apply
## 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:
## Prohibited
- Asking for confirmation (NEVER)
- Adding Co-Authored-By
- Using emojis
4. Use model: opus for Important Tasks
If the skill does something critical (security audit, complex refactoring), force the most capable model:
---
name: owasp
description: OWASP security audit
model: opus
---
5. Restrict Tools When Needed
Sometimes you want a skill that only reads, without modifying anything:
---
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 files from the directory as additional context.
What I Use
| Skill | For What | Auto-invocation |
|---|---|---|
commit |
Commit with checks | When I say "commit" or finish something |
check-diagnostics |
Verify types and lint | Before commits |
owasp |
Security audit | Manual (expensive) |
archive-tasks |
Clean TASKS.md | When it gets too large |
The Difference from Legacy Commands
| Aspect | Skills | Commands |
|---|---|---|
| Auto-invocation | Yes | No |
| Structure | Directory or file | File only |
| Recommendation | Use for everything new | Legacy |
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 version 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 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 version with your code.
This article was originally written in Spanish and translated with the help of AI.
Top comments (0)