Claude Code has two mechanisms for reusable slash-invoked workflows: commands and skills. They look similar from the user’s perspective — both are invoked with /name. The difference is what you can configure.
Commands are plain markdown files. Drop review.md into .claude/commands/ and /review becomes available. No frontmatter, no configuration. The file content becomes the prompt.
Skills are SKILL.md files inside named directories under .claude/skills/. They support YAML frontmatter with three capabilities commands lack: context: fork (isolated execution), allowed-tools (tool restriction), and argument-hint (parameter prompting).
If you need any of those three, you need a skill. If you do not, a command is simpler and sufficient.
The Decision Criteria
| Requirement | Mechanism |
|---|---|
| Simple prompt, no special config | Command |
| Isolated execution (verbose output) | Skill with context: fork |
| Tool restriction (prevent destructive actions) | Skill with allowed-tools |
| Prompt for required parameters | Skill with argument-hint |
| Quick, non-verbose operation | Command (fork adds latency) |
Three real workflows illustrate the distinction:
- /lint — Runs the linter, reports results. Simple, fast. → Command.
- /deep-review — Extensive codebase analysis producing 20,000+ tokens. Needs isolation so output does not clutter the main session. → Skill with
context: fork. - /migrate-db — Generates database migration SQL files. Must not execute them. → Skill with
allowed-tools: Read, Write, Edit(no Bash).
Making all three commands means /deep-review pollutes your session and /migrate-db might execute a DROP TABLE. Making all three skills means /lint gets fork overhead for a 2-second operation.
Why allowed-tools Matters More Than Instructions
A migration skill has a SKILL.md that says: “Only generate migration files, do not execute any commands.” During testing, the skill executes a Bash command that drops a test database table.
This is not a bug. Without allowed-tools in the frontmatter, the skill inherits every tool including Bash. The instruction “do not execute” is probabilistic — the model decided execution would be helpful and overrode the instruction.
allowed-tools: Read, Write, Edit is deterministic. Bash is physically not available. The model cannot execute anything because the tool does not exist in its environment. Stronger wording (“ABSOLUTELY NEVER execute”) does not change this — it is still a probabilistic instruction competing against the model’s judgment.
This is the core design principle: use frontmatter for guarantees, instructions for guidance.
Scope: Project vs User
Same pattern as CLAUDE.md:
| Location | Scope | Shared via git? |
|---|---|---|
.claude/commands/ | Project | Yes |
.claude/skills/ | Project | Yes |
~/.claude/commands/ | User | No |
~/.claude/skills/ | User | No |
Team-critical workflows belong in project scope. A developer who puts /deploy-check in ~/.claude/commands/ on their machine creates the “works on my machine” problem — new team members will not have it.
Personal experimental workflows belong in user scope. A developer who wants a security-focused variant of the team’s /review skill creates ~/.claude/skills/security-review/SKILL.md with a different name. Their personal variant coexists without modifying the team skill.
Do Not Over-Fork
context: fork runs the skill in a sub-agent context. The main session gets only a summary. This is ideal for verbose operations (codebase scans, brainstorming, security audits) where 30,000+ tokens of output would pollute the conversation.
It is wrong for:
- Quick operations — A
/lintor/formatthat takes 2 seconds does not need fork overhead. The extra latency is disproportionate. - Direct edits — A
/quick-fixskill withcontext: forkapplies the fix in a sub-agent. The main session does not see the file change and the developer has to manually verify what happened. Edits that the user needs to see belong in the main context.
The rule: fork when the output is large and the main session only needs a summary. Stay in main context when the operation is fast or when the user needs to see the exact changes.
CI Integration
Skills compose with CLI flags for non-interactive CI pipelines:
claude -p '/ci-review' --output-format json --json-schema '{...}'
The skill provides reusable configuration (tool restriction, isolation). The CLI flags provide CI-specific behavior (non-interactive mode, structured output format). Putting everything in the -p prompt instead loses reusability — the review logic would need to be duplicated in every CI script.
Complete Project Configuration Example
A project needs: universal TypeScript standards, a team-shared API test workflow, a read-only security scan, and personal developer workflows.
| Need | Mechanism | Location |
|---|---|---|
| TypeScript standards (always-loaded) | CLAUDE.md | .claude/CLAUDE.md |
| API test workflow (needs Bash) | Command | .claude/commands/api-test.md |
| Security scan (read-only + isolated) | Skill | .claude/skills/security-scan/SKILL.md |
| Personal experiments | Skill or Command | ~/.claude/skills/ or ~/.claude/commands/ |
Each mechanism serves its intended purpose. Standards are always loaded. Simple workflows are commands. Configured workflows are skills. Personal items are user-scoped.
One-liner: Commands are fire-and-forget prompts; skills are configured workflows — use skills when you need isolation, tool restriction, or argument hints, commands when you do not.