{"slug": "custom-slash-commands-in-claude-code-how-they-work-now-that-commands-are-skills", "title": "Custom slash commands in Claude Code: how they work now that commands are skills", "summary": "Anthropic has merged custom slash commands into skills in Claude Code, making commands a subset of the skills system. The change means commands like `/fix-issue` now support argument expansion, tool grants that last only one turn, and shell command substitution via `!` syntax. A developer explains the new behavior, including precedence rules where skills override commands, and practical tips for using `$ARGUMENTS`, indexed access, and `allowed-tools`.", "body_md": "You type `/fix-issue 123`\n\nand 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/`\n\nfiles 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.\n\nHere is the current behavior, verified against the official docs as of late July 2026.\n\nDrop a markdown file into your project:\n\n``` php\n<!-- .claude/commands/fix-issue.md -->\n---\ndescription: \"Fix a GitHub issue by number\"\n---\nFix GitHub issue $ARGUMENTS following our coding standards.\nRead the issue with `gh issue view`, locate the relevant code,\nimplement the fix, and add a regression test.\n```\n\nTyping `/fix-issue 123`\n\nsends that content with `$ARGUMENTS`\n\nreplaced by `123`\n\n.\n\nThe merge means this file and a skill at `.claude/skills/fix-issue/SKILL.md`\n\nboth create `/fix-issue`\n\nand behave the same way. What a skill adds on top:\n\nIf a command and a skill share a name, the skill takes precedence. My practical rule: throwaway personal shortcuts can stay in `commands/`\n\n; anything you will grow or share belongs in `skills/`\n\n. (How to write a description that actually triggers is its own topic — I covered it in [the SKILL.md guide](https://dev.to/rulestack/skillmd-how-to-write-a-claude-code-skill-that-actually-triggers-format-template-27cp).)\n\n`$ARGUMENTS`\n\n, `$0`\n\n, and the quoting rules\nThree expansion rules cover almost every confusion I see:\n\n**1. $ARGUMENTS is the whole string as typed.**\n\n`/fix-issue 123 high-priority`\n\n→ `$ARGUMENTS`\n\nbecomes `123 high-priority`\n\n.**2. Indexed access is zero-based and shell-quoted.** `$ARGUMENTS[0]`\n\n(or the shorthand `$0`\n\n) is the *first* argument. Quoting groups words:\n\n``` php\n/migrate-component \"search bar\" React Vue\n→ $0 = search bar, $1 = React, $2 = Vue\n```\n\n**3. Missing values behave differently by placeholder type.** An indexed placeholder with no matching argument (`$2`\n\nwhen 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`\n\n), escape it: `\\$1.00`\n\n.\n\nOne more safety net: if you pass arguments but the file contains no `$ARGUMENTS`\n\nat all, Claude Code appends `ARGUMENTS: <your input>`\n\nto the end, so your input is never silently dropped.\n\n`!` command``\n\nThis is the feature that turns a canned prompt into a grounded one. Lines like:\n\n```\n## Current diff\n!`git diff HEAD`\n```\n\nrun 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.\n\nSubstitution is a single pass: a command's output cannot emit another `!`…``\n\nplaceholder for a second expansion. Treat it as data, not macros.\n\n`allowed-tools`\n\nis a one-turn grant, not a session setting\nThe most common surprise in the merged model. Frontmatter like:\n\n```\nallowed-tools: Bash(git add:*), Bash(git commit:*)\n```\n\npre-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.\n\nTwo related notes:\n\n`allowed-tools`\n\ndoesn't restrict anything. Unlisted tools remain available under your normal permission settings.`${CLAUDE_SKILL_DIR}`\n\nexpands in both the body and `allowed-tools`\n\n, 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](https://dev.to/rulestack/claude-code-permission-rules-how-allow-deny-and-ask-actually-1cid).\n\nBy default both you and Claude can run any skill. Two frontmatter switches change that:\n\n`disable-model-invocation: true`\n\n— only you can trigger it. Use for anything with side effects where timing matters: `/deploy`\n\n, `/commit`\n\n, `/send-release-notes`\n\n. You don't want the model deploying because the code \"looks ready\".`user-invocable: false`\n\n— only Claude can load it. Use for background knowledge that isn't a meaningful action, like a `legacy-system-context`\n\nskill.Skill *descriptions* are always in context so Claude knows what exists; the *body* loads on invocation.\n\nOnce 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:\n\nAfter 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](https://dev.to/rulestack/what-survives-compaction-in-claude-code-and-how-to-keep-your-rules-alive-564p).\n\n`$0`\n\nis the `$N`\n\nstays literal; unmatched named args become empty`!` command``\n\nruns before Claude reads anything; single pass`allowed-tools`\n\nclears on your next message`code-review`\n\n) `.claude/commands/`\n\nstill 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*\n\n*Daily notes on AI coding workflows live on Bluesky: https://bsky.app/profile/ai-shop.bsky.social*", "url": "https://wpnews.pro/news/custom-slash-commands-in-claude-code-how-they-work-now-that-commands-are-skills", "canonical_source": "https://dev.to/rulestack/custom-slash-commands-in-claude-code-how-they-work-now-that-commands-are-skills-425k", "published_at": "2026-07-23 00:29:43+00:00", "updated_at": "2026-07-23 01:29:39.520882+00:00", "lang": "en", "topics": ["developer-tools", "large-language-models", "ai-tools"], "entities": ["Anthropic", "Claude Code"], "alternates": {"html": "https://wpnews.pro/news/custom-slash-commands-in-claude-code-how-they-work-now-that-commands-are-skills", "markdown": "https://wpnews.pro/news/custom-slash-commands-in-claude-code-how-they-work-now-that-commands-are-skills.md", "text": "https://wpnews.pro/news/custom-slash-commands-in-claude-code-how-they-work-now-that-commands-are-skills.txt", "jsonld": "https://wpnews.pro/news/custom-slash-commands-in-claude-code-how-they-work-now-that-commands-are-skills.jsonld"}}