Tutorials
Ren Okabe13 min read29 views

Claude Code Slash Commands in 2026: Custom Commands Are Now Skills

Anthropic merged custom slash commands into skills in 2026. Your .claude/commands files still work, nothing is deprecated, and the frontmatter you gain is the reason to move. Includes the command-name rule that catches everyone and a runnable collision audit.

A laptop screen in a darkened room showing a code editor with a project file tree on the left and syntax-highlighted source on the right, lit in blue and purple, August 2026
A laptop screen in a darkened room showing a code editor with a project file tree on the left and syntax-highlighted source on the right, lit in blue and purple, August 2026
On this page

Quick answer

As of August 2026, custom slash commands in Claude Code are skills. Anthropic merged the two: a file at .claude/commands/deploy.md and a skill at .claude/skills/deploy/SKILL.md both create /deploy and behave the same way. Your existing .claude/commands/ files keep working, so nothing is deprecated and nothing needs migrating. What changed is that the skill form unlocks frontmatter you cannot express in a bare command file: disable-model-invocation to stop Claude triggering a deploy on its own, allowed-tools to pre-approve a script, named arguments, a model and effort override, and a directory for supporting files. The single most common trap is that in a personal or project skill the frontmatter name field does NOT set the command you type. The directory name does.

Anthropic logo This guide is written against the Claude Code documentation as it stood in August 2026, and every behavioural claim below is from code.claude.com/docs/en/skills or the commands reference.

The thing most guides have not caught up with

If you search for Claude Code slash commands today you will find a lot of good writing that describes two separate systems: slash commands as simple single-file shortcuts, and Agent Skills as the bigger multi-file thing with auto-discovery. That model was accurate. It is now out of date.

Anthropic's documentation states it plainly: custom commands have been merged into skills. There is a visible artefact of the merge you can check yourself. The old documentation URL code.claude.com/docs/en/slash-commands still returns HTTP 200, but the page it serves is the skills page, byte for byte, and its canonical link points at /docs/en/skills. There is no HTTP redirect and no deprecation banner anywhere on either page. Merged, not deprecated, is the correct reading.

One of the better independent write-ups on this, prg.sh's Claude Code slash commands note, still carries a comparison table calling slash commands "Manual only" against skills as "Auto-discovered + manual". That distinction no longer tracks the directory you chose. Invocation is now a frontmatter decision, and you can make a .claude/commands/ file manual-only or a skill manual-only using the same field.

So there are three kinds of thing behind a / in 2026, and it is worth being precise about which one you are dealing with:

Scroll to see more

KindWhere it comes fromExample
Built-in commandCoded into the CLI/clear, /model, /context, /diff, /resume
Bundled skillShips with Claude Code, prompt-based/doctor, /code-review, /debug, /batch, /loop
Your ownA file you write in your repo or home directory/deploy, /fix-issue

Only the third kind is what this guide builds. The first two are catalogued in Anthropic's commands reference, and bundled skills are marked Skill in its Purpose column. You can turn bundled skills off with the disableBundledSkills setting, which disables all of them except /doctor.

Prerequisites

  • Claude Code installed and authenticated, v2.1.218 or later if you want background: false and the relaxed boolean parsing described below
  • A repository you are willing to add a .claude/ directory to
  • python3 on PATH if you want to run the audit script in Step 6

Check your version first, because several behaviours below changed at specific releases:

bash
claude --version

Write the simplest possible command

The shortest path is still a single markdown file. Create it and it works immediately, with no restart and no registration step.

bash
mkdir -p .claude/skills/changelog
cat > .claude/skills/changelog/SKILL.md <<'EOF'
---
description: Summarise commits since the last tag as a changelog entry
---

Summarise the commits since the most recent git tag into a short changelog.

Group the entries under Added, Changed and Fixed. Skip merge commits and
dependency bumps. Write it as markdown I can paste into CHANGELOG.md.
EOF

Type /changelog in a session in that repo and it runs. Note what is not in that file: no name, no registration, no config entry. Every frontmatter field is optional. Only description is recommended, because that is what Claude reads to decide whether the skill is relevant.

The equivalent single-file form still works too, and creates exactly the same /changelog:

bash
mkdir -p .claude/commands
# same body, no directory needed
cp .claude/skills/changelog/SKILL.md .claude/commands/changelog.md

Do not actually keep both. If a skill and a command share a name, the skill wins, which is the first precedence rule worth memorising.

Know where the command name comes from

This is the field that trips people up, and it is worth stating flatly because the frontmatter reference is easy to misread.

In a personal or project skill, the name field sets only the display label shown in skill listings. The command you type comes from the directory name. Renaming name: does not rename your command.

Scroll to see more

Skill locationCommand name comes fromResult
.claude/skills/deploy-staging/SKILL.mdDirectory name/deploy-staging
~/.claude/skills/deploy-staging/SKILL.mdDirectory name/deploy-staging
.claude/commands/deploy.mdFile name without extension/deploy
my-plugin/skills/review/SKILL.mdFrontmatter name, namespaced/my-plugin:review

Plugins are the exception where name does real work: it replaces the last segment, so my-plugin/skills/review/SKILL.md with name: fancy becomes /my-plugin:fancy. Before v2.1.216 the frontmatter name replaced the whole command, so older plugin write-ups showing a bare /fancy in the menu are describing pre-v2.1.216 behaviour.

Decide where the file lives

Location is an access-control decision, not a stylistic one.

Scroll to see more

LocationPathApplies to
Personal~/.claude/skills/NAME/SKILL.mdAll your projects
Project.claude/skills/NAME/SKILL.mdThis project only, and it is committed
PluginPLUGIN/skills/NAME/SKILL.mdWherever the plugin is enabled
EnterpriseManaged settingsEveryone in the organisation

When names collide across levels, enterprise overrides personal, and personal overrides project. That ordering surprises people who expect the most specific scope to win: a deploy skill in your home directory beats the one your team committed to the repo. If you are debugging a teammate reporting different behaviour from the same repo, check their ~/.claude/skills/ before anything else.

A skill at any of those levels also overrides a bundled skill of the same name, but not its aliases. Name a project skill code-review and it replaces the bundled /code-review, while typing the bundled alias /review will never reach your version.

Nested directories are the genuinely useful part in a monorepo. A skill at apps/web/.claude/skills/deploy/SKILL.md that clashes with a root deploy stays available as /apps/web:deploy, and Claude picks the variant matching the files it is actually touching.

Take arguments properly

Everything after the command name becomes the argument text. There are four ways to read it, and the named form is the one worth reaching for in anything you will still be maintaining next quarter.

markdown
---
description: Migrate a component from one framework to another
argument-hint: [component] [from] [to]
arguments: [component, from, to]
---

Migrate the $component component from $from to $to.
Preserve all existing behaviour and its tests.

Running /migrate-component SearchBar React Vue fills those in by position. The alternatives are $ARGUMENTS for the whole string, $ARGUMENTS[0] for a positional index, and $0 as shorthand for the same index. Named arguments map to positions in the order you declare them, so arguments: [issue, branch] makes $issue the first and $branch the second.

Two behaviours that are easy to get wrong:

  • Indexed arguments use shell-style quoting. /my-skill "hello world" second gives $0 the value hello world and $1 the value second. Without the quotes you get two separate arguments and a silently wrong result.
  • If you pass arguments to a skill whose body has no $ARGUMENTS placeholder anywhere, Claude Code does not discard them. It appends ARGUMENTS: your input to the end of the content. That is a reasonable fallback, but it means a typo in your placeholder degrades quietly instead of failing.

argument-hint is separate from arguments and only drives the autocomplete hint. Setting one does not set the other.

Control who is allowed to invoke it

This is the capability a plain command file never had, and it is the main reason to reach for the skill form.

By default both you and Claude can invoke anything. Two fields change that:

markdown
---
description: Deploy the application to production
disable-model-invocation: true
---

Deploy $ARGUMENTS to production:

1. Run the test suite
2. Build the application
3. Push to the deployment target
4. Verify the deployment succeeded

The full matrix:

Scroll to see more

FrontmatterYou can invokeClaude can invokeDescription in context
(default)YesYesAlways
disable-model-invocation: trueYesNoNo
user-invocable: falseNoYesAlways

Use disable-model-invocation: true for anything with side effects: commit, deploy, send a message, open a PR. You do not want Claude deciding your branch looks ready. If Claude tries anyway, Claude Code blocks the call and tells it not to reproduce the steps another way, so the failure mode is Claude suggesting you run /deploy yourself rather than quietly doing it by hand.

Use user-invocable: false for background knowledge that is not an action. A legacy-system-context skill explaining how an old service behaves should be available to Claude when relevant, but /legacy-system-context is not a meaningful thing for a human to type.

There is a context-cost side effect worth knowing. Skill descriptions sit in context so Claude knows what exists; full bodies load only on invocation. Setting disable-model-invocation: true also keeps the description out of context entirely, so manual-only skills are free until used. The combined description and when_to_use text is truncated at 1,536 characters in the listing, so put the key use case first.

Audit what your project actually exposes

Precedence rules only bite when you cannot see them. This script enumerates every skill and command file that could produce a / command for the current project, resolves the command name using the Step 2 rules, and reports collisions with the winner marked.

Save it as audit_skills.py and run it from your repository root.

python
#!/usr/bin/env python3
"""Audit Claude Code skills and commands visible from this project.

Resolves each file's command name using the 2026 rules: skill directory
name wins for personal and project skills, file stem for .claude/commands
files, and a skill beats a command of the same name.
"""
from pathlib import Path

LEVELS = [
    ("personal", Path.home() / ".claude"),
    ("project", Path.cwd() / ".claude"),
]
# Enterprise sits above both but lives in managed settings, not on this path.
PRECEDENCE = {"personal": 0, "project": 1}


def discover():
    found = []
    for level, root in LEVELS:
        for skill in sorted(root.glob("skills/*/SKILL.md")):
            found.append((skill.parent.name, "skill", level, skill))
        for cmd in sorted(root.glob("commands/*.md")):
            found.append((cmd.stem, "command", level, cmd))
    return found


def rank(entry):
    """Lower sorts first and therefore wins."""
    _, kind, level, _ = entry
    return (PRECEDENCE[level], 0 if kind == "skill" else 1)


def main():
    entries = discover()
    if not entries:
        print("No skills or commands found.")
        return

    by_name = {}
    for entry in entries:
        by_name.setdefault(entry[0], []).append(entry)

    collisions = 0
    for name in sorted(by_name):
        group = sorted(by_name[name], key=rank)
        winner = group[0]
        marker = "" if len(group) == 1 else "  <-- COLLISION"
        print(f"/{name}{marker}")
        for entry in group:
            _, kind, level, path = entry
            flag = "WINS " if entry is winner else "hidden"
            try:
                shown = path.relative_to(Path.cwd())
            except ValueError:
                shown = path
            print(f"    {flag}  {level:8} {kind:7}  {shown}")
        if len(group) > 1:
            collisions += 1

    print(f"\n{len(by_name)} command names, {collisions} collision(s).")


if __name__ == "__main__":
    main()

Run it:

bash
python3 audit_skills.py

On a clean project with the changelog skill from Step 1 you get one line. On a real repository the collisions are the interesting output: they are the cases where someone's home directory is silently overriding a committed team skill.

The script deliberately does not try to resolve enterprise-managed skills or plugin namespaces. Enterprise skills are not on a path it can read, and plugin skills carry a plugin-name:skill-name prefix that means they cannot collide with either level.

Stack several in one message

Since v2.1.199 you can invoke more than one skill at the start of a message. Typing /write-tests /fix-issue 123 loads both and passes 123 to each as $ARGUMENTS. Claude Code expands the first skill plus up to five more.

Expansion stops at the first token that is not an inline user-invocable skill. Two things end the run early and become argument text instead: a skill that runs as a forked subagent (/code-review does, from v2.1.218), and a skill whose arguments may themselves start with a slash, such as /loop. Before v2.1.199 only the first skill loaded at all and the rest arrived as literal text, which is why older examples of this pattern do not work as described.

If you plan to share it, restrict the frontmatter

This is the trap that costs the most time, because it fails at the very end of the process.

Claude Code accepts every frontmatter field. The Agent Skills open standard does not. If you upload a skill to claude.ai, publish through the Skills API, or package with package_skill.py, only six fields are allowed: name, description, license, compatibility, metadata and allowed-tools.

Anything else is a hard error, not a silently ignored field:

text
Unexpected key(s) in SKILL.md frontmatter: argument-hint. Allowed properties
are: allowed-tools, compatibility, description, license, metadata, name

So argument-hint, arguments, disable-model-invocation, user-invocable, model, effort, context, paths and hooks are all Claude Code extensions. They are fine, and they are the reason to use skills at all, but a skill that uses them is a Claude Code skill rather than a portable one. Decide which you are writing before you build a workflow around a field you will have to strip later.

Limitations and open questions

  • The rendered body is pinned for the session. When a skill is invoked, its rendered content enters the conversation as one message and stays for the rest of the session. Claude Code does not re-read the file on later turns. Editing a skill mid-session does not update the copy already in context, so write standing guidance rather than one-time steps, and start a new session after a meaningful edit.
  • Permissions do not persist the way instructions do. An allowed-tools grant clears when you send your next message, even though the skill body stays loaded. A long-running task will start prompting again partway through, and that is intended behaviour, not a bug.
  • Precedence runs opposite to most tooling. Personal beating project is unusual, and there is no warning when it happens. That is the entire reason for the Step 6 script.
  • Version-sensitive behaviour is dense here. Skill stacking, plugin naming, boolean parsing and forked /code-review all changed at specific v2.1.x releases during 2026. If something behaves unlike the documentation, check claude --version before assuming the documentation is wrong.
  • Untested here: whether enterprise-managed skills expose any local artefact an audit script could read. If they do, the Step 6 script is incomplete and I would like to know.

If you are deciding between a skill and a subagent for a piece of work, the boundary is roughly whether you need a separate context window, and I worked through that in the guide to creating and scoping Claude Code subagents. And if the thing you are about to turn into a skill currently lives as a growing section of CLAUDE.md, what actually loads into each session covers why moving it is usually the right call: a skill body costs nothing until it is used, while CLAUDE.md is paid for on every single session.

Ren Okabe

Written by

Ren Okabe

Ren builds agent infrastructure and writes copy-paste tutorials for engineers shipping LLM tool-use systems.

Frequently asked questions

Are Claude Code slash commands deprecated?

No. As of August 2026 custom slash commands have been merged into skills, which is not the same as deprecation. A file at .claude/commands/deploy.md and a skill at .claude/skills/deploy/SKILL.md both create /deploy and work the same way, and existing .claude/commands files keep working with no migration required. There is no deprecation banner on Anthropic's documentation. The old /docs/en/slash-commands URL still returns HTTP 200, but it now serves the skills page and its canonical link points at /docs/en/skills.

What is the difference between slash commands and skills in Claude Code?

In 2026 they are the same system. The difference is what the file format lets you express. A single .claude/commands/NAME.md file gives you a prompt and nothing else. A skill directory at .claude/skills/NAME/SKILL.md gives you the same command plus optional frontmatter: disable-model-invocation to stop Claude invoking it automatically, user-invocable to hide it from you, allowed-tools to pre-approve commands, named arguments, model and effort overrides, and a directory for supporting files.

Why does my Claude Code slash command have the wrong name?

Because in a personal or project skill the frontmatter name field sets only the display label in skill listings, not the command you type. The command comes from the directory name, so .claude/skills/deploy-staging/SKILL.md is always /deploy-staging regardless of what name says. Plugin skills are the exception: there the name field does replace the last segment of the namespaced command.

Which skill wins when two have the same name?

Enterprise overrides personal, and personal overrides project. A deploy skill in your home directory therefore beats one your team committed to the repository, which is the opposite of what most tooling does. A skill also beats a command file of the same name, so with both .claude/commands/deploy.md and .claude/skills/deploy/SKILL.md present, /deploy runs the skill. Plugin skills use a plugin-name:skill-name namespace and cannot collide.

How do I pass arguments to a Claude Code slash command?

Use $ARGUMENTS for the whole argument string, $ARGUMENTS[0] or the shorthand $0 for a positional index, or declare an arguments list in frontmatter and reference the names directly, such as $component. Indexed arguments use shell-style quoting, so wrap multi-word values in quotes. If you pass arguments to a skill whose body contains no $ARGUMENTS placeholder, Claude Code appends them as ARGUMENTS: your input rather than discarding them.

Tutorials

Claude Code Subagents: How to Create, Scope, and Nest Them (2026)

The /agents creation wizard was removed in Claude Code v2.1.198, so almost every guide still ranking for this topic teaches a flow that no longer exists. Here is the current way to write, scope, invoke, and cap Claude Code subagents, with a version number on every claim. August 2026.

15 min read75
Tutorials

Claude Code Plan Mode: How It Actually Gates Your Edits (2026)

Plan mode is not a read-only state. It is a rule at step 4 of a six-step permission evaluation, which is why allow rules stop applying while you plan and why a session with bypass permissions can edit anyway. The CLI keystrokes, the settings, the Agent SDK equivalent, and a plan-then-execute pipeline in Python and TypeScript. August 2026.

14 min read76