Turn a prompt you keep retyping into a custom /slash-command
Arguments, live shell output injected before Claude reads a word, and a switch that stops Claude from pulling the trigger itself. The cheapest packaging in Claude Code.
A custom slash command is a prompt you have saved to a file and invoke by typing /name. In current Claude Code, custom commands and skills are the same mechanism: a file at .claude/commands/standup.md and a skill at .claude/skills/standup/SKILL.md both create /standup and behave identically. Your existing .claude/commands/ files keep working untouched; the skill form just adds a directory for supporting files and a few frontmatter switches. Either way the payoff is the same — the instructions you keep retyping live in one file instead of your muscle memory.
The reason to build one is context economy, not just convenience. A command's body loads only when you invoke it, so a long procedure costs almost nothing until the moment you need it — unlike a CLAUDE.md section, which sits in context on every turn. If a chunk of your CLAUDE.md has quietly turned into a checklist rather than a fact, that is the signal to move it into a command. Three features are what make this better than copy-paste, and they stack.
Arguments. Put $ARGUMENTS in the body and everything you type after the command name is substituted in: /fix-issue 412 sends “Fix GitHub issue 412 following our coding standards…” For positional values use $0, $1, $2 — /migrate-component SearchBar React Vue maps cleanly into three slots. Wrap multi-word values in quotes so they land in a single slot. This is the line between a command and a snippet: the command takes input.
Dynamic context injection is the feature most people miss, and the strongest one. A line beginning with !`command` runs in your shell before Claude sees the prompt, and its output replaces the placeholder. A /summarize-changes command whose body opens with !`git diff HEAD` arrives with your actual diff already inlined, so Claude reasons about your real working tree instead of guessing from open files. The same shape with !`gh pr diff` gives you a PR reviewer that always sees current data. The command does the gathering; you type six characters. This is preprocessing — Claude never runs the command, it only sees the rendered result.
Invocation control decides who gets to pull the trigger. By default both you and Claude can invoke a command. Add disable-model-invocation: true and only you can — which is exactly what you want for anything with side effects: /deploy, /commit, /send-slack-message. You don't want Claude deciding the code looks ready and shipping it. Pair it with allowed-tools: Bash(git commit *) so the command runs the calls you have already blessed without stopping to ask.
The trap is the injection syntax, which is stricter than it looks. The !`...` form only fires when ! sits at the start of a line or immediately after whitespace; write KEY=!`cmd` and it stays literal text, silently un-run. And because that line executes on every invocation, treat a project command checked into a shared repo as code-review surface — a command can grant itself broad tool access and run arbitrary shell, so read one before you trust the repository it arrived in. Set disableSkillShellExecution: true in settings if you want to turn the behavior off for non-bundled sources entirely.
The try-it block builds a working /summarize-changes command, live diff injection and all, in under a minute.
In any git repo, create a personal command that summarizes your uncommitted changes and flags anything risky. The directory name becomes the command you type.
mkdir -p ~/.claude/skills/summarize-changes
cat > ~/.claude/skills/summarize-changes/SKILL.md <<'EOF'
---
description: Summarize uncommitted changes and flag anything risky. Use when asked what changed or for a commit message.
---
## Current changes
!`git diff HEAD`
## Instructions
Summarize the changes above in two or three bullets, then list any
risks you notice: missing error handling, hardcoded values, tests that
need updating. If the diff is empty, say there are no uncommitted changes.
EOF
Now edit any file, start claude, and invoke it:
/summarize-changes
The !`git diff HEAD` line runs before Claude reads the prompt, so the summary is grounded in your actual working tree — not what Claude can infer from open files. Add $ARGUMENTS to the body and a scope, e.g. /summarize-changes the auth refactor, to steer what it focuses on.