LEARN · PHASE 05 · LESSON 39

Why Custom Commands Merged Into Skills

You can explain why your `.claude/commands/deploy.md` still works, name the

Type
Learn
Prerequisites
Lesson 38, Built-in Commands
Time
~30 minutes

Learning Objectives

  • Say why a legacy command file and a skill produce the same /name
  • Name the four capabilities a command file cannot express
  • Pick the migration moment for one of your own command files — and the moment to leave it alone
  • Explain "custom commands have been merged into skills" without overclaiming

The Problem

You wrote .claude/commands/ship-it.md last year. It worked. It runs tests, builds, deploys staging.

Then you need three things, one at a time:

  1. The deploy needs to run a script that lives next to the instructions. The command file can't reference it — the file is the whole thing.
  2. It needs the file inside it, because .md is one blob.
  3. Claude keeps triggering it during unrelated work, because a command file has a description that goes largely unread.

You convert it twice, both times by hand, both times half-successfully. Nothing told you the two formats had converged — one just quietly stopped being the right shape.

The cost is small each time and adds up to an afternoon. The bigger cost is not knowing why the new format is better, so you convert again next year.


The Concept

The docs say it in one line:

Custom commands have been merged into skills.

That means two things, and people routinely hear only the first:

flowchart LR
    A["Legacy command<br/>.claude/commands/deploy.md"] --> M
    B["Skill<br/>.claude/skills/deploy/SKILL.md"] --> M
    M["/deploy runs"]
    M --> N["Skills add four things<br/>command files can't express"]
    style N fill:#1f6f4a,color:#fff
Mermaid source — renders as a diagram where mermaid.js is available

One: equivalence. Both files create and run the same command:

.claude/commands/deploy.md
.claude/skills/deploy/SKILL.md

Both produce /deploy. A legacy file is not deprecated-and-broken. It works. The docs are explicit that existing .claude/commands/ files continue to work, and that there is no hard removal or breaking-change notice — this is a soft direction, with backward compatibility.

Two: supersession. Skills accept every field a command file accepts, and two more — name and paths. A command file is a skill with the folder removed.

The four things a command file cannot do

CapabilityWhy the old shape can't
Supporting filesA command is one .md. A skill is a directory, so it ships scripts, references, templates alongside the instructions.
Frontmatter controlsdisable-model-invocation doesn't exist on a command file. You cannot say "only run this when I ask."
Automatic invocationOnly skills get matched against description by the model. Commands wait for you.
Dynamic context injectionSkills can run ` !command and `` `! ``` blocks to load fresh state at invoke time.

That last one is the quiet one. A command file is a frozen prompt: whatever it says about "the current branch" was true when you wrote it. A skill can ask.

When to migrate

MigrateLeave it alone
You need scripts or reference files next to the instructionsIt's a three-line prompt with no state
You need it to run only when askedYou want it to be purely manual already and it works
It's going to a teammate, or into a pluginIt works and nothing about it is hurting you
You're editing it again anywayIt's shared across many files and a rename is risky — migration gains nothing here

New work goes to .claude/skills/<name>/SKILL.md. The docs say so directly.

The reframe: a command file was always a skill that had its folder

deleted — the merge isn't a new feature bolted on, it's you getting the

folder back, which is where the scripts and the trigger rules were hiding.

Legacy files accept the same frontmatter except name and paths. Their command name comes from the filename. That's the whole of the compatibility surface.

Verified against the skills page on 2026-10-15. The direction is stable; the field list moves. Check the page before you quote a field count.


Build It

1. Run the same thing twice

Create both files in a scratch project, identical body text:

.claude/commands/warmup.md
.claude/skills/warmup/SKILL.md

Run /warmup from each.

✅ You pass when both run and produce the same command name from two different directory shapes.

2. Find the field that proves the difference

Add disable-model-invocation: true to the skill. Leave it out of the command file. Try both against a request that would plausibly match.

✅ You pass when you can say which one the model may choose on its own and which one it may not, from observation rather than from the docs.

3. Confirm the two-field gap

Check whether a legacy command file honours name and paths. Put a name: in the frontmatter of warmup.md and see whether it changes anything.

✅ You pass when you can state whether name and paths are the two fields the legacy shape drops, and you observed it rather than assumed it.

4. Try to add a supporting file

Create a script next to warmup.md and have the prompt reference it. Then move the prompt into the skill folder and reference it from there.

✅ You pass when the first arrangement cannot reach the file and the second can, and you can say which one that is.

5. Audit your own command files

List every .claude/commands/*.md you own. Mark each: migrate, or leave.

✅ You pass when every file has a verdict and a one-line reason, and at least one is "leave it alone".


Use It

ThingWhen you reach for it
.claude/skills/<name>/SKILL.mdAll new work
.claude/commands/<name>.mdExisting file that works; leave it
Migrate when you need a folderScripts, references, templates
Migrate when you need a trigger ruledisable-model-invocation
"Merged"Equivalence today, supersession tomorrow. Not removal.
name / pathsSkill-only fields; a command file ignores them

Exercises

  1. Take one real command file you own. Write the migration as a diff — file move, frontmatter change, nothing else — and apply it. Run the command before and after. ✅ You pass when the command still runs and you can point at the one line of the diff that actually bought you something.
  1. Write a one-paragraph explanation of "custom commands have been merged into skills" for a colleague who has never heard of either format. Then find one sentence in it that would still be true if the format later gets removed. ✅ You pass when you can quote that sentence and say why it is the one that survives.
  1. Find one legacy command file — yours, a colleague's, or an installed one — and find something it does that the skill form would let you do better. ✅ You pass when you have named the capability and shown the command file structurally cannot express it, not just "the skill is newer".

Key Terms

TermWhat people sayWhat it actually means
merged"slash commands are gone"They produce the same command. Legacy files still work; skills are the direction.
legacy command"the old format"A single .md in .claude/commands/. Works. Loses name and paths.
skill"the new format"A directory with a SKILL.md. Same invocation, plus a folder to put things in.
soft migration"it's deprecated"Backward compatible, with a preferred direction. No removal date.
automatic invocation"the model finds it"Skills only, matched against description. Command files wait for your slash.

What this lesson skipped

Every frontmatter field. Twenty-odd of them, and choosing between them is the whole of the next two lessons. Lesson 41 is the table.

Where skills live — personal, project, nested, plugin, synced. Lesson 43.

context: fork. A skill can run in a forked subagent context. That is Phase 06 (Subagents); the field appears here so the shape is familiar, but nothing in this lesson depends on it.

Dynamic context injection syntax. ` !command and `` `! ``` blocks were named, not taught. They show up for real in Lesson 44.

Plugin distribution. A plugin ships skills, but the packaging layer is Phase 09.

Check yourself

3 questions. Answer before you reveal — recognising the right option is not the same as being able to produce it.

  1. Your team's release command lives in .claude/commands/release.md and has worked for a year. Which statement about it is actually true today?

    1. It still runs as /release, but name and paths in its frontmatter are not honoured — skills accept those two fields, command files do not
    2. It still runs as /release, but it is deprecated and will stop loading on the next major version
    3. It no longer loads at all; use .claude/skills/release/SKILL.md instead or the command disappears
    4. It still runs as /release, but the model can now auto-invoke it from its description, which it could never do before
    Show answer

    A. It still runs as /release, but name and paths in its frontmatter are not honoured — skills accept those two fields, command files do not

    The merge is equivalence plus supersession: legacy files keep working, and the two fields they lack are name and paths. Option B overclaims — the docs describe a soft direction with backward compatibility and no removal notice. Option C is the failure mode people actually fear and it is not what shipped; existing .claude/commands files continue to work. Option D inverts the capability: automatic, description-matched invocation is one of the things skills add, so a command file still cannot do it.

  2. Your deploy command needs to run ./scripts/preflight.sh before deploying, and the script already exists next to the project root. What is the actual blocker with keeping the command file?

    1. Command files cannot invoke scripts at all, so this has to become a hook
    2. The script path would be relative to the wrong file, since a command's frontmatter has no working-directory field
    3. A command file is a single .md — it has no folder to hold or reference a sibling script, which is exactly what the skill directory restores
    4. Nothing blocks it — the command can just inline the shell command instead of calling the script
    Show answer

    C. A command file is a single .md — it has no folder to hold or reference a sibling script, which is exactly what the skill directory restores

    The blocker is structural, not behavioural: a command is one markdown blob, so the skill's directory is what lets supporting files travel with the instructions. Option A reaches for hooks, which are a different phase and would be the wrong tool for a per-invocation precondition. Option B invents a field that does not exist. Option D is tempting and wrong in practice — inlining the script body duplicates logic that already has a home, and it is precisely the drift the docs warn about when they describe shipping scripts with skills.

  3. You have 14 legacy command files across a repo. What is the honest migration posture?

    1. Migrate all 14 now — anything in .claude/commands/ is second-class and will rot
    2. Migrate none — the folder format is deprecated and skills are a separate product
    3. Migrate on contact: convert a file when it needs a folder, a trigger rule, or a teammate to receive it; leave the rest
    4. Migrate only the ones with under 30 lines, since short prompts have the most to gain from fields
    Show answer

    C. Migrate on contact: convert a file when it needs a folder, a trigger rule, or a teammate to receive it; leave the rest

    Migrate-on-contact is the posture that matches the docs: new work prefers skills, existing files work fine, and each of the four added capabilities is a concrete trigger for converting one specific file. Option A treats working files as rot and spends a rename across 14 files for no gain. Option B inverts the direction — skills are where the format is heading, not commands. Option D is arbitrary; nothing about prompt length determines which fields you can use.