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.
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/modelchange how the session behaves without leaving the prompt. - It separates the things you trigger from the things Claude may trigger.
disable-model-invocation: trueon a command file means Claude cannot decide to run your deploy.
How it works
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 reportUnknown command.02Built-in commands execute in the CLI
They never reach the model.
/cleardiscards the conversation,/permissionsopens a dialog,/statusprints session state. Nothing is added to the context window.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.
04Arguments are substituted before Claude sees the body
$ARGUMENTSexpands to everything you typed after the name.$0,$1, and named placeholders declared in theargumentsfrontmatter map to positional values with shell-style quoting. An indexed placeholder with no matching argument is left in the text unchanged.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.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-nameand 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
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.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.
03Declare the arguments you expect
Add
argument-hint: [pr-number]so autocomplete shows the shape, andarguments: prso you can write$prin the body instead of$0.04Inline the state the command needs
Use `
!git diff --stagedor!gh pr diff $pr` rather than telling Claude to fetch it. One less tool call, and no chance of it fetching the wrong thing.05Lock down who can run it
Set
disable-model-invocation: truefor anything with side effects. Addallowed-toolsfor the specific tools it needs so the run does not stop for permission prompts.
Examples
---
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.# 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'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:deploywhen 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
/deploymid-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
$0and$1.FIXIndexed arguments use shell-style quoting. Run
/my-command "hello world" secondso$0ishello 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-stylein v2.1.91,/vimin v2.1.92,/pr-commentsin v2.1.91. Checkclaude --versionand the commands reference.SYMPTOMA command works locally but not in
claude -p.FIXTerminal-only built-ins such as
/loginare unavailable in-p. User-invocable skills and custom commands do work: include/namein 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-toolsfor exactly the calls the body makes, so the run does not stall on permission prompts. - Set
disable-model-invocation: trueon 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.Create
.claude/commands/explain-file.mdwithdescription,argument-hint: [path], andarguments: path. - 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.Start
claudeand run/explain-file package.json. - 4.Check the transcript: the file contents were already inlined, so Claude never called the Read tool.
- 5.Run
/explain-filewith no argument and watch$pathstay literal — proof that unmatched placeholders are left unchanged.
Related concepts
Verified against code.claude.com/docs/en/commands on 2026-08-09. See content/SOURCES.md for the full table.
← / → MOVE BETWEEN CONCEPTS