06 / 20Workflow6 MIN READ

Commands

A command is a /-prefixed instruction typed at the start of a message that either runs fixed logic inside Claude Code or expands into a prompt Claude acts on.

A slash command typed into a terminalA terminal window with a prompt line. A slash command is typed after the prompt caret, and the expanded instructions appear below it as output.>/review 1234

What it is

Type / in a session and Claude Code lists what is available. Most entries are built-in commands whose behaviour is coded into the CLI: /clear empties the conversation, /compact summarises it, /model switches models, /permissions opens the rule editor. Some entries are bundled skills, marked Skill in the reference — /doctor, /code-review, /debug, /loop, /simplify, /run, /verify. Those are prompts handed to Claude rather than fixed logic, which is why Claude can invoke some of them on its own.

Your own commands are skills. A file at .claude/commands/deploy.md and a directory at .claude/skills/deploy/SKILL.md both create /deploy and accept the same frontmatter. The commands/ form still works and is the shortest path for a single-file prompt; the skills form adds a directory for supporting files and lets Claude load it automatically.

A command is only recognised at the start of a message, and everything after the name becomes its arguments. Skills are the exception: /skill-a /skill-b do XYZ loads every skill named at the front, up to six, and passes the trailing text to each as arguments. A command sent while Claude is responding queues until the turn finishes, except for a few — /status, /tasks, /usage — that run immediately.

What it does for you

  • It gives a multi-paragraph prompt a name. The review instructions you paste weekly become /review-api, and the wording stops drifting between runs.
  • It puts session control where your hands already are. /context, /compact, /clear, and /model change how the session behaves without leaving the prompt.
  • It separates the things you trigger from the things Claude may trigger. disable-model-invocation: true on a command file means Claude cannot decide to run your deploy.

How it works

  1. 01Claude Code parses the leading token

    If the message starts with / followed by a known name, it is a command. Text later in the message is just text. Unknown names report Unknown command.

  2. 02Built-in commands execute in the CLI

    They never reach the model. /clear discards the conversation, /permissions opens a dialog, /status prints session state. Nothing is added to the context window.

  3. 03Custom commands and bundled skills expand into a prompt

    The file body is injected as a user message at the point of invocation. Because it is appended rather than inserted, the cached prompt prefix stays intact.

  4. 04Arguments are substituted before Claude sees the body

    $ARGUMENTS expands to everything you typed after the name. $0, $1, and named placeholders declared in the arguments frontmatter map to positional values with shell-style quoting. An indexed placeholder with no matching argument is left in the text unchanged.

  5. 05Bash injection runs at expansion time

    A ` !command ` line in the body is executed and replaced by its output before the prompt reaches Claude, so the instructions arrive with live state already inlined.

  6. 06Name collisions resolve by scope

    Enterprise overrides personal, personal overrides project, and any of them override a bundled skill of the same name. Plugin commands are namespaced as plugin-name:command-name and cannot collide. If a skill and a command share a name, the skill wins. A nested .claude/skills/ directory that reuses a root-level name gets a directory-qualified command such as /apps/web:deploy, so both stay reachable from the same session.

How to implement it

  1. 01Decide the scope

    Put it in .claude/commands/ or .claude/skills/ to share it with the repository, or in ~/.claude/ to carry it between projects.

  2. 02Write the body as instructions, not documentation

    The file becomes a prompt. Say what to do, in what order, and what to output. Skip the explanation of why the procedure exists.

  3. 03Declare the arguments you expect

    Add argument-hint: [pr-number] so autocomplete shows the shape, and arguments: pr so you can write $pr in the body instead of $0.

  4. 04Inline the state the command needs

    Use ` !git diff --staged or !gh pr diff $pr ` rather than telling Claude to fetch it. One less tool call, and no chance of it fetching the wrong thing.

  5. 05Lock down who can run it

    Set disable-model-invocation: true for anything with side effects. Add allowed-tools for the specific tools it needs so the run does not stop for permission prompts.

Examples

.claude/commands/review.mdmarkdown
---
description: Reviews the current diff, or a pull request if you pass its number, against this repository's conventions.
argument-hint: [pr-number]
arguments: pr
disable-model-invocation: true
allowed-tools: Read, Grep, Glob, Bash(git diff *), Bash(git log *), Bash(gh pr diff *)
---

## Diff under review

!`if [ -n "$pr" ]; then gh pr diff "$pr"; else git diff origin/main...HEAD; fi`

## Instructions

Review the diff above. Read the surrounding code for every file it touches —
a diff alone does not show whether a guard already exists upstream.

Check in this order and stop at the first section with findings:

1. **Correctness.** Off-by-one errors, unhandled error paths, and any `nil`
   or `undefined` that can reach a call site.
2. **Contracts.** Money is `BigDecimal` in minor units. Service objects expose
   `call` and return a `Result`. Controllers never touch a model directly.
3. **Tests.** Every new controller action has a request spec. A behaviour
   change without a matching test change is a finding.
4. **Migrations.** No edits to a migration older than the last release tag.

For each finding give `file:line`, one sentence on what breaks, and the
smallest fix. Rank most severe first. If the diff is clean, say so in one
line — do not pad the report.
A project review command. Invoke it as `/review` for the working tree or `/review 1234` for a pull request.
Using itbash
# In a session: review the working branch
/review

# Review a specific pull request
/review 1234

# Chain two skills and pass the trailing text to both
/review /summarize-changes focus on the billing module

# Non-interactive runs expand user-invocable commands too
claude -p "/review 1234" --output-format json | jq -r '.result'
The same file works in an interactive session and in a scripted run.

Use it when

  • A review prompt whose wording must not drift between runs or between teammates.
  • A release procedure you want triggered by hand at a specific moment.
  • A scaffolding command that writes a new module following the repository’s existing shape.
  • A debugging entry point that inlines the failing test output before Claude starts reasoning.
  • A per-package command in a monorepo, namespaced automatically as apps/web:deploy when the name collides with a root-level one.

Avoid it when

  • You would run it once. Writing the file, testing both invocation paths, and remembering the name costs more than typing the prompt.
  • The instruction should apply to every turn. That is CLAUDE.md or an output style; a command only exists while you are typing it.
  • The step must happen automatically at a lifecycle point. A hook fires on the event; a command waits for you.
  • The body is a fact rather than a procedure. Facts belong in CLAUDE.md or a path-scoped rule, which load without you remembering a name.

Common mistakes

  • SYMPTOMYou type /deploy mid-sentence and it is treated as plain text.

    FIXA command is only recognised at the start of a message. Put it first; everything after the name becomes its arguments.

  • SYMPTOMA multi-word argument is split across $0 and $1.

    FIXIndexed arguments use shell-style quoting. Run /my-command "hello world" second so $0 is hello world.

  • SYMPTOMClaude runs your deploy command on its own because the request looked related.

    FIXAdd disable-model-invocation: true. Claude Code then blocks the call and tells Claude not to reproduce the steps another way.

  • SYMPTOMA built-in command you rely on reports Unknown command.

    FIXAvailability depends on version, plan, and platform, and some commands have been removed — /output-style in v2.1.91, /vim in v2.1.92, /pr-comments in v2.1.91. Check claude --version and the commands reference.

  • SYMPTOMA command works locally but not in claude -p.

    FIXTerminal-only built-ins such as /login are unavailable in -p. User-invocable skills and custom commands do work: include /name in the prompt string and Claude Code expands it before the run.

Best practices

  • Give every custom command a description that reads as a trigger, since the same file can be loaded automatically as a skill.
  • Inline the state the command operates on with ` !... ` instead of asking Claude to go find it.
  • Add allowed-tools for exactly the calls the body makes, so the run does not stall on permission prompts.
  • Set disable-model-invocation: true on anything that deploys, commits, or sends a message.
  • Keep bodies short — an invoked body stays in context for the rest of the session.
  • Check / autocomplete after adding a file; a name that does not appear means the file is in the wrong directory.

Try it in five minutes

Write a command that inlines live state and takes an argument.

  1. 1.Create .claude/commands/explain-file.md with description, argument-hint: [path], and arguments: path.
  2. 2.Make the body start with a ` !cat $path ` line, followed by instructions to explain the file in five bullet points aimed at a new teammate.
  3. 3.Start claude and run /explain-file package.json.
  4. 4.Check the transcript: the file contents were already inlined, so Claude never called the Read tool.
  5. 5.Run /explain-file with no argument and watch $path stay literal — proof that unmatched placeholders are left unchanged.

Verified against code.claude.com/docs/en/commands on 2026-08-09. See content/SOURCES.md for the full table.

← / → MOVE BETWEEN CONCEPTS