Custom slash commands in Claude Code: how they work now that commands are skills

작성자

카테고리:

← 피드로
DEV Community · Rulestack · 2026-07-23 개발(SW)

Rulestack

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.

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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`

Enter fullscreen mode Exit fullscreen mode

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:*)

Enter fullscreen mode Exit fullscreen mode

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-tools doesn’t restrict anything. Unlisted tools remain available under your normal permission settings.
  • ${CLAUDE_SKILL_DIR} expands in both the body and allowed-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 a legacy-system-context skill.

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:

  1. Every line is a recurring token cost. State what to do; skip the essay on why.
  2. 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, deterministicallya 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

  • $0 is the first argument (zero-based), quotes group words
  • unmatched $N stays literal; unmatched named args become empty
  • !`command` runs before Claude reads anything; single pass
  • allowed-tools clears 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

원문에서 계속 ↗

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다