DEV Community

Fernando Rodriguez
Fernando Rodriguez

Posted on • Originally published at frr.dev

Claude Code Skills: Teaching New Tricks to an Old Dog

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:

  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
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

description

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

  1. Decide when to auto-invoke the skill
  2. 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.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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:*)
---
Enter fullscreen mode Exit fullscreen mode

context: fork

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

context: fork
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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...]
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

Flexible Skill (Claude Decides)

---
name: review
description: Reviews code for issues
---

Analyze the code and suggest improvements.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode


bash
git branch --show-current


- If NOT `main` → **ABORT** with "Only from main"

## Step 2: Clean state

Enter fullscreen mode Exit fullscreen mode


bash
git status --porcelain


- If there's output → **ABORT** with "Uncommitted changes"

## Step 3: Bump + push

Enter fullscreen mode Exit fullscreen mode


bash
uv run bump2version patch
git push && git push --tags

Enter fullscreen mode Exit fullscreen mode


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
Enter fullscreen mode Exit fullscreen mode


markdown

SKILL.md:

---
name: deploy
description: Deploys to production
---

Execute:

Enter fullscreen mode Exit fullscreen mode


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


Report the result. **DO NOT modify the script.**
Enter fullscreen mode Exit fullscreen mode


shell

deploy.sh:

#!/bin/bash
set -e
uv run pytest || exit 1
hugo --minify
rsync -avz public/ user@server:/var/www/
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode


bash
git diff --name-only HEAD


### Phase 2: MANDATORY checks

Enter fullscreen mode Exit fullscreen mode


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`
Enter fullscreen mode Exit fullscreen mode


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
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
---
Enter fullscreen mode Exit fullscreen mode

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
---
Enter fullscreen mode Exit fullscreen mode

Simple vs Complex Skills

A simple skill is a single file:

.claude/skills/review.md
Enter fullscreen mode Exit fullscreen mode

A complex skill is a directory with resources:

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

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)