You type /fix-issue 123 and Claude Code expands it into a full prompt with your team's standards attached. Custom slash commands are the cheapest automation in Claude Code — one markdown file, zero config — and they changed in a way that's easy to miss: custom commands have been merged into skills. Your .claude/commands/ files keep working, but the merged model explains several things that look like bugs (arguments not expanding, tool grants vanishing mid-task) and adds features commands never had.
Here is the current behavior, verified against the official docs as of late July 2026.
The one-file version still works
Drop a markdown file into your project:
<!-- .claude/commands/fix-issue.md -->
---
description: "Fix a GitHub issue by number"
---
Fix GitHub issue $ARGUMENTS following our coding standards.
Read the issue with `gh issue view`, locate the relevant code,
implement the fix, and add a regression test.
Typing /fix-issue 123 sends that content with $ARGUMENTS replaced by 123.
The merge means this file and a skill at .claude/skills/fix-issue/SKILL.md both create /fix-issue and behave the same way. What a skill adds on top:
- a directory for supporting files (templates, scripts, reference docs Claude loads only when needed)
- frontmatter to control who invokes it — you, Claude, or both
- automatic invocation: Claude can load it when your conversation matches its description
If a command and a skill share a name, the skill takes precedence. My practical rule: throwaway personal shortcuts can stay in commands/; anything you will grow or share belongs in skills/. (How to write a description that actually triggers is its own topic — I covered it in the SKILL.md guide.)
Arguments: $ARGUMENTS, $0, and the quoting rules
Three expansion rules cover almost every confusion I see:
1. $ARGUMENTS is the whole string as typed. /fix-issue 123 high-priority → $ARGUMENTS becomes 123 high-priority.
2. Indexed access is zero-based and shell-quoted. $ARGUMENTS[0] (or the shorthand $0) is the first argument. Quoting groups words:
/migrate-component "search bar" React Vue
→ $0 = search bar, $1 = React, $2 = Vue
3. Missing values behave differently by placeholder type. An indexed placeholder with no matching argument ($2 when you passed one arg) stays in the text unchanged. A named placeholder declared in frontmatter expands to an empty string. If you need a literal dollar-digit in prose ($1.00), escape it: \$1.00.
One more safety net: if you pass arguments but the file contains no $ARGUMENTS at all, Claude Code appends ARGUMENTS: <your input> to the end, so your input is never silently dropped.
Live data injection: !`command`
This is the feature that turns a canned prompt into a grounded one. Lines like:
## Current diff
!`git diff HEAD`
run the shell command before Claude sees anything, and the output replaces the placeholder. Claude receives the actual diff, not an instruction to go fetch it — one fewer tool round-trip, and no chance of the model "summarizing" a diff it never read.
Substitution is a single pass: a command's output cannot emit another !`…` placeholder for a second expansion. Treat it as data, not macros.
allowed-tools is a one-turn grant, not a session setting
The most common surprise in the merged model. Frontmatter like:
allowed-tools: Bash(git add:*), Bash(git commit:*)
pre-approves those tools for the turn that invokes the skill. The grant clears when you send your next message — even though the skill's content stays in context. So "why is Claude asking permission again for the same command?" is usually this: the instructions persisted, the grant did not. Re-invoking the skill re-applies it.
Two related notes:
-
allowed-toolsdoesn't restrict anything. Unlisted tools remain available under your normal permission settings. -
${CLAUDE_SKILL_DIR}expands in both the body andallowed-tools, so a skill can ship a script and pre-approve exactly that script's invocation — no prompt, no wildcard.
If you want a grant that survives the whole session, that belongs in your permission rules instead — the allow/deny/ask matching logic is its own minefield.
Deciding who can invoke it
By default both you and Claude can run any skill. Two frontmatter switches change that:
-
disable-model-invocation: true— only you can trigger it. Use for anything with side effects where timing matters:/deploy,/commit,/send-release-notes. You don't want the model deploying because the code "looks ready". -
user-invocable: false— only Claude can load it. Use for background knowledge that isn't a meaningful action, like alegacy-system-contextskill.
Skill descriptions are always in context so Claude knows what exists; the body loads on invocation.
The body persists — write it like standing orders
Once invoked, the rendered content stays in the conversation for the rest of the session (re-invoking an identical skill adds a short "already loaded" note rather than a duplicate). Two consequences:
- Every line is a recurring token cost. State what to do; skip the essay on why.
- Write instructions as standing rules ("always run X after editing Y"), not one-time steps — Claude does not re-read the file on later turns.
After context compaction, recent skills are re-attached within a fixed token budget, so a long session can silently drop older ones — details in what survives compaction.
When a slash command is the wrong tool
- A fact Claude should always know → one line in CLAUDE.md.
- A procedure you keep pasting → a skill. That's the sweet spot.
- Something that must happen every time, deterministically → a hook. Skills rely on the model choosing to comply; hooks don't ask.
- Reference material too big for context → skill with supporting files, referenced from SKILL.md and loaded on demand.
Quick gotcha checklist
-
$0is the first argument (zero-based), quotes group words - unmatched
$Nstays literal; unmatched named args become empty -
!`command`runs before Claude reads anything; single pass -
allowed-toolsclears on your next message - a project skill named like a bundled one (
code-review) replaces it -
.claude/commands/still works; skills are the recommended path
I maintain Rulestack — versioned rules & skills packs for Claude Code, Cursor, and Codex, kept in sync with changes like this one: https://rulestack.gumroad.com?ref=devto
Daily notes on AI coding workflows live on Bluesky: https://bsky.app/profile/ai-shop.bsky.social
Top comments (0)