Claude Code custom commands: turning a good prompt into a tool

A custom command is a prompt you have already debugged. The value is not the typing it saves, it is that the fifth run is identical to the first.

By the Continuum team. We build a workbench that runs Claude Code, Codex, and their peers, so the model rates quoted here are the ones our own cost analytics ship with.

The short version

A Markdown file at .claude/commands/deploy.md becomes /deploy, and so does a skill at .claude/skills/deploy/SKILL.md. Custom commands were merged into skills, so both work and the skill form is the one gaining features. Arguments arrive as $ARGUMENTS, or by position as $ARGUMENTS[N] and its $N shorthand. A backtick-wrapped ! block runs a shell command and injects its output before the prompt is sent, which is what lets a command gather its own context.

What you need to know
  • Commands are now skills. .claude/commands/*.md still works and still becomes /name.
  • $ARGUMENTS for everything, $ARGUMENTS[N] or $N by position. $0 is the first.
  • A ! shell block runs before the prompt is sent, so the command gathers its own context.
  • allowed-tools grants permission for the turn. It does not restrict; that is disallowed-tools.
  • disable-model-invocation: true for anything with side effects, so only you can fire it.

Custom commands are skills now

The most important thing to know in August 2026 is that custom commands have been merged into skills. Nothing you already wrote broke: a file at .claude/commands/deploy.md still creates /deploy and still behaves the same. What changed is that the skill form is where new capability lands.

Two layouts, one command.

LayoutCommandGets you
.claude/commands/deploy.md/deployA single file. Everything below still applies
.claude/skills/deploy/SKILL.md/deployA directory for supporting files, invocation control, and automatic loading when relevant

Where they live, and who wins on a name clash.

ScopePathPrecedence
EnterpriseManaged settings directoryHighest
Personal~/.claude/skills/<name>/SKILL.mdOverrides project
Project.claude/skills/<name>/SKILL.mdOverrides bundled
Plugin<plugin>/skills/<name>/SKILL.mdNamespaced as /plugin:name, cannot clash

The mechanics

01

Create the file

.claude/skills/<name>/SKILL.md for the project, or ~/.claude/skills/<name>/SKILL.md for yourself. The directory name becomes the command.

02

Write the frontmatter before the body

A description so it appears usefully in the listing, an argument-hint so autocomplete shows what it expects, and disable-model-invocation: true if it has side effects.

03

Make it gather its own context

Replace "look at the diff" with a shell block that puts the diff in the prompt. That is the single change that turns a saved prompt into a tool.

04

Run it, then tighten it

The first version will do one thing you did not want. Add the "do not" line for exactly that, then commit it so everyone gets the fixed version.

.claude/skills/fix/SKILL.md - most of the syntax in one file.
---
description: Fix a failing test
argument-hint: [test file path]
arguments: [testfile]
disable-model-invocation: true
allowed-tools: Read Edit Bash(npm run test *)
model: sonnet
---

Fix the failing test in `$testfile`.

Current failure:
!`npm run test -- "$0" 2>&1 | tail -40`

Recent changes to that area:
!`git log --oneline -5 -- "$0"`

Rules:
- Fix the implementation, not the test, unless the test asserts
  something genuinely wrong. Say which you chose and why.
- Do not weaken an assertion to make it pass.
- Re-run the single test to confirm before reporting done.

Argument substitutions. Every field is optional.

SyntaxExpands to
$ARGUMENTSEverything after the command name
$ARGUMENTS[0]The first argument, zero-indexed
$0, $1Shorthand for the same, so $0 is the first argument
$nameA named argument declared in the arguments frontmatter list, mapped in order
${CLAUDE_PROJECT_DIR}The project root
${CLAUDE_SKILL_DIR}The directory holding this SKILL.md, for bundled scripts
${CLAUDE_SESSION_ID}The current session id, for logging
${CLAUDE_EFFORT}The active effort level

Arguments stack too. Typing /write-tests /fix-issue 123 loads both skills and passes 123 to each. Claude Code expands the first skill plus up to five more, and stops at the first token that is not an inline user-invocable skill.

Four worth stealing

/review - check your own work before pushing

.claude/skills/review/SKILL.md
---
description: Review uncommitted changes for defects
disable-model-invocation: true
allowed-tools: Read Grep Glob Bash(git diff *) Bash(git status)
disallowed-tools: Edit Write
---

Review these uncommitted changes:

!`git diff HEAD`

Report only defects that will actually occur, each with the input that
triggers it. Ignore style and naming. If there is nothing real, say so
in one line rather than inventing something. Do not fix anything.

/commit - a message that describes the change

.claude/skills/commit/SKILL.md
---
description: Write a commit message for staged changes
disable-model-invocation: true
allowed-tools: Bash(git diff *) Bash(git log *)
model: haiku
---

Staged changes:
!`git diff --cached`

Recent history, for style:
!`git log --oneline -10`

Write a conventional commit message. Subject under 72 characters.
Body only if the change needs a why. Output the message alone, no
commentary and no code fences.

/explain - orient in unfamiliar code

.claude/skills/explain/SKILL.md
---
description: Explain what a file does and who calls it
argument-hint: [file path]
arguments: [target]
allowed-tools: Read Grep Glob
context: fork
agent: Explore
---

Explain `$target`.

Cover, briefly:
1. What it is responsible for, in two sentences.
2. Who calls it. Grep for the imports.
3. What it depends on.
4. Anything surprising a newcomer would get wrong.

Do not summarise line by line.

/tests - coverage for something that already works

.claude/skills/tests/SKILL.md
---
description: Add unit tests for a file
argument-hint: [file path]
arguments: [target]
allowed-tools: Read Write Glob Bash(npm run test *)
model: haiku
---

Write unit tests for `$target`.

Existing conventions in a neighbouring test file:
!`ls "$(dirname "$0")" | head -20`

Match those conventions exactly; do not introduce a new style. Test the
public interface, cover the error paths, and never modify the
implementation. Run the tests before reporting done.

allowed-tools grants. It does not restrict.

This is the field most people have backwards, and getting it wrong is why a "read-only" review command starts editing files.

What each field actually does, as of August 2026.

FieldEffectLifetime
allowed-toolsPre-approves the listed tools so Claude uses them without prompting. Every other tool is still callable under your normal permission rulesThe turn that invoked the skill
disallowed-toolsRemoves the listed tools from the pool while the skill is activeUntil your next message
permissions.deny in settingsBlocks the tool everywhereAlways

Deciding who can fire it

Two frontmatter fields control invocation.

FrontmatterYou can invokeClaude can invokeWhen it loads
(default)YesYesDescription always in context; body loads on invoke
disable-model-invocation: trueYesNoNothing in context until you invoke it
user-invocable: falseNoYesDescription always in context

Set disable-model-invocation: true on anything with side effects: /deploy, /commit, /send-slack-message. You do not want Claude deciding to deploy because the code looks ready. It has a second benefit worth knowing: the description is then kept out of context entirely, so a library of manual commands costs nothing until used.

Running one in its own context

Some commands produce a lot of intermediate noise: a codebase survey, a dependency audit, a PR summary that reads three gh outputs. Setting context: fork runs the command in a subagent, so only its answer comes back to your conversation.

.claude/skills/pr-summary/SKILL.md
---
description: Summarize changes in a pull request
context: fork
agent: Explore
background: false
allowed-tools: Bash(gh *)
---

## Pull request context

- Diff: !`gh pr diff`
- Comments: !`gh pr view --comments`
- Changed files: !`gh pr diff --name-only`

## Your task

Summarize what this PR changes and why, in under 200 words. Then list
anything a reviewer should look at closely, with file and line.

The two fields that go with context: fork.

FieldPurpose
agentWhich subagent type runs it, for example Explore
backgroundDefaults to true. Set false to wait for the result in the same turn

What makes one good

  1. Gather context in the command. A command that runs git diff for itself beats one that asks the agent to.
  2. Restrict with disallowed-tools, not allowed-tools. The first restricts; the second only grants.
  3. Name your arguments. arguments: [target] plus $target is immune to the zero-based counting trap.
  4. Pick the model. Commit messages are a Haiku job; reviews are worth a frontier model. The override lasts for the turn and is not saved to settings.
  5. Say what not to do. "Do not weaken the assertion" prevents the specific failure that makes agent-written tests worthless.
  6. Put the trigger first in the description. The description plus when_to_use is truncated at 1,536 characters in the listing, so the key use case belongs at the front.
  7. Commit them. The point is that the whole team runs the same debugged prompt.

Questions people ask

Create a Markdown file at .claude/commands/name.md, which becomes /name, or a skill at .claude/skills/name/SKILL.md, which becomes /name and can carry supporting files. Use ~/.claude/skills/ for commands available in every project.

No. Custom commands were merged into skills, and existing .claude/commands/ files keep working exactly as before. Skills are where new capability lands, so prefer them for anything new. If a skill and a command share a name, the skill takes precedence.

$ARGUMENTS captures everything after the command name. For positions, use $ARGUMENTS[N] or the $N shorthand, which is zero-based, so $0 is the first argument. Declaring arguments: [name] in frontmatter and using $name avoids the off-by-one entirely.

Yes. A backtick-wrapped ! block runs before the content reaches Claude, and its stdout replaces the placeholder, which lets a command gather its own context. Pair it with an allowed-tools Bash rule so the command does not prompt.

No, and this is the most common mistake. allowed-tools pre-approves the listed tools for the turn; it does not remove anything. Use disallowed-tools to take tools away, or run the command with context: fork so a subagent tool allowlist applies instead.

Set disable-model-invocation: true in the frontmatter. Only you can then invoke it with /name. Use it for anything with side effects, such as deploy or commit commands. It also keeps the description out of context until you invoke it.

Yes, via model in the frontmatter. The override applies for the rest of the current turn and is not saved to settings, so the session model resumes on your next prompt. Routing mechanical commands such as commit-message generation to a cheaper model is a real saving.

Project commands, yes. Sharing a debugged prompt is one of the cheapest ways to make a whole team consistent. Be aware that a project skill granting itself tools through allowed-tools applies only after a reviewer accepts the workspace trust dialog.

Sources

Every figure above was read from these pages on August 2026. Vendors reprice without notice; if you find a stale number, tell us.

  1. Claude Code: extend Claude with skills
  2. Claude Code commands reference
  3. Claude Code permissions reference
Try it

Your commands,
one keystroke away.

Continuum reads the same command and skill directories and surfaces them in a palette as you type.

free app · your subscriptions · local-first