Free

From Slash Commands to Skills: When to Migrate and How

Custom commands have merged into Skills. When to migrate to `.claude/skills/`, and what you gain when you do.


The previous three posts walked through the full story of slash commands: starting from a Markdown file, then !command injection, then orchestrating subagents and MCP. None of that is obsolete — but Anthropic quietly merged two things behind the scenes: custom commands have been folded into Skills.

Your .claude/commands/review.md still runs, /review still works, nothing breaks. But if you want a command to grow real capabilities — multiple files, fork execution, auto-activation by path — the new home is .claude/skills/review/SKILL.md. This post answers two questions: how to migrate, and whether it's worth it.


The minimal migration: change one path, leave the rest alone

Anthropic's compatibility promise is explicit: .claude/commands/deploy.md and .claude/skills/deploy/SKILL.md both register as /deploy, with identical behavior. If there's a name conflict, skills win.

Minimal steps:

cd .claude/commands
mkdir -p ../skills/review
mv review.md ../skills/review/SKILL.md

Not a single line of the file needs to change. YAML frontmatter, !`command`, @file, $ARGUMENTS — all still work.

But if that's all you do, the obvious question is: why bother? The answer is that a skill directory can hold many files, while a command is stuck with one.


Why migrate: the four things SKILL.md gives you

1. Supporting files: move long docs out of the main file

SKILL.md is the entry point, and the same directory can hold arbitrary supporting files. Picture a layout like this:

.claude/skills/review/
├── SKILL.md            # entry point — short instructions + references
├── checklist.md        # the long code-review checklist
├── examples/
│   └── good-diff.md    # a positive example
└── scripts/
    └── lint.sh         # a script SKILL.md can call

Reference them from SKILL.md with relative paths:

Review the current diff, following the standards in [checklist.md](checklist.md).
See [examples/good-diff.md](examples/good-diff.md) for the expected output format.

These supporting files don't get loaded into context automatically — Claude reads them on demand. Anthropic recommends keeping SKILL.md under 500 lines and pushing everything else into supporting files.

2. disable-model-invocation: which skills only you can trigger

By default, Claude will invoke any skill whenever it seems appropriate. That's dangerous for commands with side effects/deploy, /commit, /send-email. You don't want Claude "deciding the code looks fine and deploying it."

---
name: deploy
description: Deploy to production
disable-model-invocation: true
allowed-tools: Bash(kamal deploy:*), Bash(git push:*)
---

With that one line, /deploy only fires when you type it. Claude won't reach for it mid-conversation.

The opposite knob is user-invocable: false — "only Claude can invoke this, and it won't appear in the / menu." Good for background-knowledge skills (e.g. legacy-system-context: Claude loads it automatically when relevant, but there's no reason for you to fire it manually).

3. context: fork: run the skill in a subagent

This is the biggest upgrade. Last post showed how a command can use allowed-tools: Task to get the model to spawn subagents — but you had to describe "spin up an Explore subagent to do X" inside the prompt yourself.

A skill handles it with one frontmatter line:

---
name: deep-research
description: Deep dive into a symbol
context: fork
agent: Explore
---

Research every use of $ARGUMENTS:
- all call sites
- business scenarios
- alternative implementations

Return a summary of ≤300 words.

When you fire /deep-research SomeClass, the entire SKILL.md becomes the prompt for a fresh subagent, running with the Explore agent type. Once it finishes, only the conclusion comes back. The main conversation's context stays completely clean.

It turns "spawning a subagent" from prose inside your prompt into a declarative property of the skill.

4. paths: auto-activate by file type

---
name: rails-conventions
description: Rails coding conventions for this project
paths: ["app/**/*.rb", "config/**/*.rb"]
---

Follow the Rails conventions of this project:
- use service objects instead of fat controllers
- ActiveRecord scopes must be named
...

When you're editing app/models/user.rb, this skill gets added to context automatically; when you're editing package.json, it doesn't. More precise than CLAUDE.md's always-on background — think of it as "CLAUDE.md layered by path."


In practice: promoting /plan to a skill

The /plan example from the last post was a single-file command:

.claude/commands/plan.md

Promoted to a skill:

.claude/skills/plan/
├── SKILL.md
├── research-prompt.md     # instructions for subagent 1
└── risk-prompt.md         # instructions for subagent 2

SKILL.md:

---
name: plan
description: Produce an implementation plan from a Linear ticket
disable-model-invocation: true
allowed-tools: mcp__linear__*, Task, Read, Grep, Bash(git log:*)
---

## Context

@.claude/context/architecture.md

## State

!`git log --oneline -10`

## Task

Pull the description and comments of Linear ticket $ARGUMENTS.

Use Task to spawn two subagents in parallel:
1. Code research: prompt in [research-prompt.md](research-prompt.md)
2. Risk assessment: prompt in [risk-prompt.md](risk-prompt.md)

Combine the two results and output implementation steps + risk list + suggested commit granularity.

The two subagent prompts live in separate files, so updating one doesn't touch SKILL.md. The skill itself stays crisp at under 30 lines, while the supporting files can each run hundreds of lines deep.

If you don't want to orchestrate the subagents yourself, go harder — throw the whole skill into a fork and let an Explore agent chew through it in one pass:

---
name: plan
context: fork
agent: Explore
allowed-tools: mcp__linear__*
---

Fire /plan ENG-4213 → an Explore agent in its own context takes the entire SKILL.md as its task → only the final plan comes back. The main conversation stays pristine.


When not to migrate

Not every command deserves a promotion. A command belongs in .claude/commands/ when:

  • A single Markdown file is enough — no checklist, examples, or scripts to split out
  • You don't need fork execution — the task is tightly coupled to the main conversation and needs the full history
  • You don't need path-based auto-activation — the user always fires it manually
  • You don't need access control — letting Claude invoke it freely is harmless

A plain /commit or /pr-desc is just a frontmatter plus a few lines of prompt — putting it in .claude/commands/ is actually clearer. Anthropic has said explicitly: both forms coexist, neither is being deprecated.


Living together

The cleanest .claude/ layout today:

.claude/
├── commands/           # lightweight: single file + simple prompt
│   ├── commit.md
│   └── pr-desc.md
├── skills/             # heavy: multi-file / fork / access control
│   ├── plan/
│   │   ├── SKILL.md
│   │   ├── research-prompt.md
│   │   └── risk-prompt.md
│   ├── deploy/
│   │   └── SKILL.md    # disable-model-invocation
│   └── rails-conventions/
│       └── SKILL.md    # paths glob auto-activation
└── context/
    └── coding-standards.md

Don't migrate everything in one sweep. The trigger is "this command is starting to bloat" — the body crosses 200 lines, docs need to be split out, you want a subagent to run it. When that day comes, changing the path is a few minutes of work.


Summary

Capability commands/ skills/
Single-file prompt shortcut yes yes
!`command` / @file / $ARGUMENTS yes yes
allowed-tools access control yes yes
Supporting files (references, scripts) no yes
disable-model-invocation guardrails no yes
context: fork + agent: isolated execution no yes
paths: glob auto-activation no yes

On the first three, commands and skills are equivalent; the last four are skills-only. Use whichever matches what you actually need.

The official path is tilting toward skills, but the compatibility promise for commands/ is explicit — this isn't a "migrate now or die" change, it's a "new capabilities offered, not forced" extension. Understand the difference, migrate when you need to, and don't chase cosmetic consistency.