Skip to content

Claude Code Slash Commands: Custom Commands, $ARGUMENTS, and Where They Went

A markdown file becomes a /command you can run with arguments — the cheapest leverage in Claude Code. But commands are a moving target since the skills merge: locations, frontmatter, and collision rules all changed. Current state, verified against the docs.

August 20, 20268 min readShift The Culture

Custom slash commands are the cheapest leverage in Claude Code: a markdown file becomes a /command you can run with arguments, and the prompt you refined over twenty attempts stops living in your paste buffer. They are also a moving target — commands were merged into the skills system, which changed where they live, what frontmatter they take, and what wins when names collide. This is the current state: the format, the argument syntax people get wrong, and the reasons a command you just wrote does not show up.

Where commands live now

Two mechanisms produce a /name you can type, and both are current:

both of these create /deploy
.claude/commands/deploy.md              # command file (project)
~/.claude/commands/deploy.md            # command file (personal, all projects)
.claude/skills/deploy/SKILL.md          # skill (project)
~/.claude/skills/deploy/SKILL.md        # skill (personal, all projects)

Anthropic's docs are explicit that custom commands have been merged into skills: a command file and a skill with the same name “both create /deployand work the same way”, existing .claude/commands/ files keep working, and skills add the extras — a directory for supporting files, control over whether you or the model invokes it, and automatic loading when the description matches the task. New work should be a skill; the command-file format below is still worth knowing because thousands of repos (ours included) carry them.

  • A command file is markdown; the body is the prompt. The file name is the command name.
  • Command files support the same frontmatter as skills — description, argument-hint, allowed-tools, model, disable-model-invocation, context — except name and paths, which are ignored in a command file.
  • For the full skill side of this — SKILL.md, descriptions that trigger auto-load, supporting files — see how to write a Claude Code skill.

Arguments: the syntax, and the off-by-one everyone hits

a command that takes arguments
---
description: Fix a GitHub issue by number
argument-hint: [issue-number]
---

Fix GitHub issue $ARGUMENTS following our coding standards.
Read the issue, implement, add tests, commit.
  • $ARGUMENTS expands to everything typed after the command name, as one string. If the placeholder is absent, arguments are still delivered — appended to the end as ARGUMENTS: <value>.
  • Positional access is zero-based. $0 (short for $ARGUMENTS[0]) is the first argument, $1 the second. Every shell-trained instinct says $1 is first; here it is not, and the failure is silent — your command just operates on the wrong value.
  • Quoting is shell-style: /my-cmd "hello world" second makes $0 = hello world.
  • An indexed placeholder with no matching argument stays in the text unchanged $2 literally appears in the prompt. Named arguments (declared with an arguments: frontmatter list, used as $issue) expand to empty instead. Two different unmatched behaviours; design for whichever you would rather debug.
  • Literal dollar amounts need escaping: write \$1.00 or the $1 inside it becomes an argument.

Dynamic context: run shell before the prompt is sent

The !`command` syntax executes a shell command when the slash command is invoked, and splices the output into the prompt in its place. This is the difference between a command that says “look at the git status” and one that already contains it:

a /commit command that arrives pre-loaded
---
description: Write a commit for the staged changes
allowed-tools: Bash(git *)
---

Current status: !`git status --short`
Staged diff: !`git diff --cached`

Write a conventional-commit message for these changes and commit.

Paths in commands should use ${CLAUDE_PROJECT_DIR} (the project root, same value hooks receive) so the command works regardless of the cwd the session happens to be in. Skills additionally get ${CLAUDE_SKILL_DIR} for files shipped next to the skill.

Why your command is not showing up

  • Wrong directory or extension. It must be .claude/commands/ (or ~/.claude/commands/) and the file must be .md. A commands/ directory at repo root, or a .txt file, is silently ignored.
  • Name collision — the skill wins. With both .claude/commands/deploy.md and .claude/skills/deploy/SKILL.md, /deployruns the skill. Across levels, a personal skill overrides a project skill with the same name. If your edit “does nothing”, check you are editing the copy that actually wins.
  • It shadows a bundled command's name but not its alias. A custom code-review replaces the bundled /code-review — but the bundled alias /review still runs the built-in, never yours.
  • user-invocable: false hides it from the / menu on purpose — that flag means only the model may load it. The inverse, disable-model-invocation: true, keeps it out of the model's hands and reserves it for you.
  • Plugin commands are namespaced/my-plugin:deploy, not /deploy. If you installed something and its command is “missing”, type the prefix.

Reference: the skills documentation (which now covers custom commands) and the built-in commands reference. If what you are really packaging is knowledge the agent should load on its own, not a command you type, write it as a skill with a good description — the dividing line is covered in the skills guide.

SharePost on X
Free · 13 pages · no upsell inside

Get the Operator Field Kit — free

Six production prompts, the five-step operator setup, and nine rules from our own failure log.

  • 6 complete prompts — printed in full, not previews
  • The five-step setup, each step with a pass/fail test
  • 9 rules from the failure log that produced them

The kit, then the occasional operator note. One click unsubscribes and we never sell the address.

Keep reading