little cups of claude

tips and techniques for Claude and Claude Code

#3: Custom Slash Commands ⌨️

Today we'll check out how to build our own slash commands in Claude Code using skills.

Custom slash commands started out as single Markdown files in a commands folder. They've since been merged into skills. Both still work, and both show up in the same / menu, but skills can do quite a bit more.

Let's get started.

Commands And Skills

A file at .claude/commands/deploy.md creates /deploy. So does a folder at .claude/skills/deploy/ with a SKILL.md inside:

# Older format (still works)
.claude/commands/deploy.md

# Skill (recommended for new work)
.claude/skills/deploy/SKILL.md
.claude/skills/deploy/checklist.md
.claude/skills/deploy/scripts/smoke.sh

The big difference is the folder. A skill can carry templates, checklists, and scripts alongside its instructions, and Claude can load a skill on its own when it's relevant, without us typing the command.

If both exist with the same name, the skill wins.

Frontmatter

Skills are configured with YAML frontmatter at the top of SKILL.md:

---
name: deploy
description: Build, test and deploy the app to production
disable-model-invocation: true
argument-hint: [environment]
allowed-tools: Bash(bun run build) Bash(bun run deploy *)
---

Deploy to $ARGUMENTS following our release checklist
in checklist.md.

A few fields worth knowing:

  • name is the command we type. It defaults to the folder name.
  • description is how Claude decides when the skill applies, so put the main use case first.
  • disable-model-invocation: true means only we can run it. Good for anything with side effects.
  • allowed-tools pre-approves tools while the skill runs, so we don't get permission prompts for them.

Arguments

Everything after the command name lands in $ARGUMENTS. For individual pieces, indexes start at zero:

---
name: fix-issue
arguments: [issue, branch]
---

Fix issue #$issue on the $branch branch.
Same thing by index: #$0 on $1.

Running /fix-issue 42 main fills in 42 and main. Quotes group words, so /fix-issue 42 "release candidate" passes two arguments, not three.

Live Context

Lines with !`command` run before Claude ever sees the skill, and the output replaces the line:

---
name: commit
description: Write a commit for the staged changes
allowed-tools: Bash(git status) Bash(git diff *) Bash(git log *) Bash(git commit *)
---

## Context
- Status: !`git status`
- Staged diff: !`git diff --cached`
- Recent commits: !`git log --oneline -10`

Write one clear commit message for these changes and commit.

Claude gets the real diff already inlined, no extra tool calls needed. Neat!

${CLAUDE_SKILL_DIR} points at the skill's own folder, which makes bundled scripts easy to call from anywhere.

Bundled Skills

Claude Code ships with a set of skills of its own, like /code-review, /batch, /debug, /loop, and /claude-api. They're good examples of how to structure longer workflows, and we can turn them off with the disableBundledSkills setting.

Personal vs Project

Project skills live in .claude/skills/ and get committed for the whole team. Personal ones live in ~/.claude/skills/ and follow us into every project:

mkdir -p ~/.claude/skills/explain

cat > ~/.claude/skills/explain/SKILL.md << 'END'
---
description: Explain code for someone new to the project
---

Explain this code for a new teammate. Cover what it does,
why it's built this way, and any gotchas.
END

Watch Out

If a !`command` line fails, the whole skill invocation stops, and Claude never sees the skill. Commands that are expected to exit non-zero need || true on the end.

Those commands also never prompt for permission. Outside auto mode, anything not already allowed makes the skill fail, which is why the commit example lists its git commands in allowed-tools.

More info about skills can be found at the Claude Code docs.

Share

Share on Threads

Similar Cups