Skills and Slash Commands: Teach It Your Workflows
Turn repeated instructions into reusable skills your whole team can invoke.
Skills and slash commands in Claude Code are file-based workflows stored in .claude/skills/ and, in the older single-file form, .claude/commands/. A Markdown file becomes a reusable /command. The runtime loads instructions on demand, using progressive disclosure to keep context small and token costs low. The same mechanism powers both manual shortcuts and automatic, multi-step procedures.
Unlike Slack slash commands that call an external server, Claude Code commands are interpreted inside the agent's own loop by the LLM (large language model). The file system is the registry. That means you version-control workflows alongside code, and the model itself decides when to load a skill's full instructions. A one-line prompt and a whole team's playbook use the same simple format.
How does Claude Code turn a Markdown file into a slash command?
The simplest form is a custom command: one Markdown file in .claude/commands/, where the filename becomes the command name and the body is a prompt template. When you type /lint src/, Claude Code reads .claude/commands/lint.md, replaces the $ARGUMENTS placeholder with whatever you typed after the command, and adds the rendered text to the conversation as a single message.
Anthropic's skills documentation now treats these command files as one kind of skill: "A file at .claude/commands/deploy.md and a skill at .claude/skills/deploy/SKILL.md both create /deploy and work the same way." Your existing command files keep working, and they accept the same frontmatter fields as a skill except name and paths. Skills are the recommended form for new workflows, mainly because a skill is a folder that can also hold scripts and reference files. If a skill and a command file share a name, the skill wins.
Think of it like a macro in an editor, but the macro is executed by the AI. Claude Code scans .claude/skills/ and .claude/commands/ in your project, and the same folders under ~/.claude/ for your personal ones, and builds a list of available commands. The description in the file's frontmatter, or failing that its first non-empty line, appears in the autocomplete menu.
Running a command takes four steps. First, your input is split into a command name and an argument string. Second, Claude Code resolves the name to a file. If the same name exists in more than one place, enterprise beats personal and personal beats project. Third, it reads the file and substitutes the arguments into placeholders like $ARGUMENTS. Fourth, it injects the resulting prompt into the conversation, and the model acts on it. From the model's point of view, it looks like you just pasted a detailed instruction block.
For our side project, .claude/commands/lint.md might contain "Run the linter on $ARGUMENTS and fix all auto-fixable issues." Typing /lint src/ turns that into a concrete task. The model then uses its tools to run the linter and apply fixes. The command itself holds no state; it is a prompt shortcut.
Skills build on this same foundation and package it as a folder. We'll see what that adds next.
What makes a skill different from a custom command?
A skill is a directory containing a SKILL.md file. The directory name becomes the slash command (/skill-name), unless you set a name in the frontmatter. The SKILL.md file starts with YAML frontmatter that describes the skill: what it does and when to use it, who may invoke it, which tools it may use without asking, and any named arguments. Every field is optional, but the docs recommend at least a description. The body holds the step-by-step instructions.
Here is a complete skill for deploying our web app to staging, saved as .claude/skills/deploy-staging/SKILL.md:
Each line of the frontmatter earns its place. disable-model-invocation: true means only you can start this skill, because you do not want Claude deciding to deploy because the code looks ready. allowed-tools lists the commands Claude may run without asking while the skill runs. ${CLAUDE_SKILL_DIR} expands to the skill's own folder, so the same script path matches in both the rule and the step. Typing /deploy-staging eu-west passes eu-west to the script.
The big leap over a plain prompt template is automatic invocation. By default, both you and Claude can invoke any skill. Claude sees each skill's description in every session, and when the current task matches it, Claude loads the skill on its own. If the description says "Use when the user asks to deploy to staging," Claude will notice that request and reach for the skill. Two fields change this: disable-model-invocation: true makes a skill yours to start, as above, and user-invocable: false hides it from the / menu so only Claude invokes it, which suits background knowledge.
- One Markdown file in .claude/commands/
- Same frontmatter, except name and paths
- No folder for scripts or references
- A folder with SKILL.md in .claude/skills/
- The recommended form for new workflows
- Can bundle scripts, references and templates
Skills can also bundle extra resources. You can add a scripts/ folder with executable code, a reference file with detailed guidelines, and templates. SKILL.md tells the model when to open each one. This turns a skill into a self-contained workflow package, not just a prompt.
For a fuller deploy workflow, the body might list the steps: run tests, build the container, push to the registry, apply Terraform. A scripts/ folder could hold the smoke-test runner, and a references/staging.md file could describe the staging environment in plain notes. Keep secrets out of the skill folder, since it is committed to the repository; read them from environment variables instead. The model loads only what it needs when it needs it.
This leads directly to the design principle that makes skills practical for large workflows: progressive disclosure.
Why does Claude Code use progressive disclosure for skills?
Every token you put into context costs money and latency. If every skill's full instructions, examples and reference documents were loaded into every session, you would burn through your context budget before any real work began. Progressive disclosure solves this by loading only a short listing up front, the body on invocation, and linked resources only when the model decides they are necessary.
The listing is small: each skill's name and description, kept short on purpose (the description is cut at 1,536 characters in the listing). It is in context in every session, which gives Claude an index of available workflows without inflating the context. When a skill is invoked, manually or automatically, the full body of SKILL.md is read and added to the conversation, where it stays for the rest of the session. That body can be long and detailed, but you pay for it only once it is used.
Even then, the scripts and reference files inside the skill directory are not loaded automatically. The instructions in SKILL.md tell the model when to open them. For example, a deploy skill might say "If the target is production, open references/production.md." That file is read only when the condition is met. This three-level disclosure (listing, body, on-demand resources) keeps the average token cost far lower than one flat prompt would. A bundled script is executed, not read, so its code never needs to enter the context at all.
In our side project, imagine a code-review skill that relies on a 50-page style guide. Pasted into CLAUDE.md, the guide would load in every session. In a skill, the guide sits in a reference file and the model opens it only when it meets a styling question. The result is a workflow that stays cheap even when the underlying knowledge base is huge.
Skills also work with external tools, and the same idea applies there: the skill names the tools, and the calls happen only when the skill runs. That's our next topic.
How do skills integrate with tools and MCP?
MCP (Model Context Protocol) connects Claude Code to external services like GitHub, Slack, or databases, and Part 5 covers it in depth. Skills teach the model how to use those connections in a structured way. You can think of MCP as the raw API (application programming interface) and skills as the playbook that orchestrates the calls.
It is important to read the tool fields in the frontmatter correctly, because they are easy to get backwards. allowed-tools pre-approves tools: Claude can use the listed ones without asking you during the turn that invokes the skill. It does not restrict anything. Every other tool stays available, and your normal permission settings still decide whether those ask first. To keep a skill away from a tool, use disallowed-tools, which removes the listed tools while the skill is active, or a permission deny rule, which blocks a tool everywhere. So a deploy skill that may run shell commands and call a Terraform MCP server, but must never send Slack messages, would pre-approve the first two in allowed-tools and list the Slack server's tools (for example mcp__slack__*) in disallowed-tools.
Be careful with skills you did not write. A project skill's allowed-tools applies even in a claude -p run in a folder you have never trusted, so a skill checked into a repository can grant itself broad access. Review the skills in a repository you clone before you run Claude Code there.
The scripts/ directory inside a skill lets you bundle executable code. The SKILL.md body can instruct the model to run a Python script to preprocess data, then interpret the output. This moves deterministic steps out of the model's reasoning and into explicit code, which is easier to audit and debug.
For our web app, a pr-review skill could use a GitHub MCP server to fetch the diff and comments. The skill's scripts/ folder might contain a linter runner. The SKILL.md body would tell the model: "Run scripts/lint.sh on the changed files, then summarize the output and any PR (pull request) discussion." The model calls the tools, but the skill defines the sequence and pre-approves only what that sequence needs.
The Agent SDK (software development kit) takes this one step further by letting other programs invoke skills. That's the final piece.
How does the Agent SDK interact with skills and commands?
Skills and command files work in non-interactive runs too. In a claude -p run or an Agent SDK session, you include /skill-name in the prompt and Claude Code expands it before running, exactly as if you had typed it, according to the headless documentation. Built-in commands that only make sense in the terminal interface, such as /login, are not available there.
The simplest form is one line:
The skill's prompt is injected, its allowed-tools are pre-approved for that turn, and the model acts. Anything else that would need your approval is denied, because nobody is there to approve it, so make sure the skill pre-approves what it needs. Skills invoked this way follow the same progressive disclosure rules.
This means our side project's CI could, after a successful build, run that command and collect the report. The deploy workflow lives in the repository, reviewed like any other code, rather than being hard-coded in a CI script.
That is the full circle: a Markdown file in your project becomes a command you can type, a skill the model can invoke when the task calls for it, and an entry point your automation can call.
Quick Reference
| Property | Value |
|---|---|
| Command file (older form, still works) | .claude/commands/<name>.md |
| Personal command file | ~/.claude/commands/<name>.md |
| Skill path | .claude/skills/<name>/SKILL.md |
| Personal skill path | ~/.claude/skills/<name>/SKILL.md |
| Frontmatter | All fields optional; description recommended |
| Arguments | $ARGUMENTS (everything), $0 and $1 (by position), or named with arguments: [env, version] and $env |
| Skill folder path | ${CLAUDE_SKILL_DIR} |
| Precedence (high to low) | Enterprise, personal, project; a skill beats a command file of the same name |
| Who can invoke | Both by default; disable-model-invocation: true (only you), user-invocable: false (only Claude) |
| Pre-approve tools | allowed-tools (for the invoking turn; restricts nothing) |
| Remove tools | disallowed-tools while the skill is active, or a permission deny rule |
| Headless use | claude -p "/skill-name arguments" |
Frequently Asked Questions
Q: How do I create a simple slash command for my project?
Create .claude/skills/lint/SKILL.md (or, in the older form, .claude/commands/lint.md) and write the prompt inside. Add a description in the frontmatter; without one, the first non-empty line is used. Use $ARGUMENTS to capture what you type after the command.
Q: Can I use arguments in a skill?
Yes. $ARGUMENTS expands to everything you typed after the command, and $0, $1 and so on pick out arguments by position. For readable names, declare them in the frontmatter, for example arguments: [env, version], and write $env and $version in the body.
Q: What's the difference between .claude/commands/ and .claude/skills/?
Command files are the older single-file form and still work; Claude Code now treats them as skills. A skill is a directory with a SKILL.md file, which adds supporting files and invocation control. Both create a /name command, and if both exist with the same name, the skill wins.
Q: How do I prevent a skill from triggering automatically?
Set disable-model-invocation: true in its frontmatter. Claude then cannot load the skill on its own, and its description is no longer placed in the context, so it costs nothing until you type /name. For the reverse case, a skill only Claude should use, set user-invocable: false.
Q: Can skills call external APIs?
Yes, through MCP servers. Add the server to Claude Code (Part 5 shows claude mcp add), then write the skill's steps around its tools. If the skill should call them without prompting, pre-approve them in allowed-tools, for example mcp__github__* for every tool from a server named github.
Test yourself
Your side project needs a skill that deploys the app to staging, runs smoke tests, and posts a summary to a Slack channel. The smoke tests are in a Python script, and the Slack integration uses an MCP server. How would you structure the skill directory and SKILL.md to keep the workflow safe, efficient and debuggable?
Answer: Create .claude/skills/deploy-staging/ with SKILL.md and a scripts/smoke_tests.py file. In the frontmatter, write a description such as "Deploy the app to staging, run smoke tests, and post a summary to Slack," and set disable-model-invocation: true, because a deploy has side effects and should start only when you type /deploy-staging. Use allowed-tools to pre-approve exactly what the steps need: the deploy command, the smoke test script (through ${CLAUDE_SKILL_DIR}), and the Slack server's posting tool. Remember that this field grants, it does not fence. In the body, write numbered steps: run the smoke tests and capture the output, deploy only if they pass, then post the results with the Slack tool. Put any notes about the staging environment in references/staging.md, and keep secrets in environment variables, never in the skill folder. The script runs without entering the context, and the explicit steps make each run easy to audit.
Next: Hooks: Run Your Rules on Every Action. It shows how to enforce a rule on every tool call, where a skill or CLAUDE.md can only ask.
If you want this kind of breakdown every week, how real systems actually work under the hood, subscribe to Internals Decoded at internalsdecoded.com.
Sources
- Extend Claude with skills
- Run Claude Code programmatically
- Configure permissions
- Agent SDK overview
- Model Context Protocol
- Slack slash commands