Mert Özen Further With Every Line
Artificial Intelligence Claude Code — #05

Skills and Slash Commands in Claude Code: Packaging Repeated Work

Mert Özen Nov 15, 2026 11 min 4 views
Skills and Slash Commands in Claude Code: Packaging Repeated Work

In Claude Code a repeated procedure belongs in a skill, not in CLAUDE.md: only its description is listed at session start and the content loads when invoked; for jobs with side effects, disable-model-invocation lets only you call it. Bundled skills, custom commands merged into skills, locations and priority, allowed-tools as a grant rather than a restriction, and the /add-migration and angular-component skills for Bookstore.

Version note: This post describes Claude Code 2.1.325 (13 November 2026); skill locations, frontmatter fields and the list of bundled skills were checked on 15 November 2026 against the Extend Claude with skills and Commands pages at code.claude.com/docs. This post is based on the official documentation. The frontmatter of the sample SKILL.md files was parsed and validated locally.

Guess First

Emre, who works on Bookstore, the online bookstore used as the shared example across this site's series, got tired of writing out the steps for adding a migration every time and wrote a skill. He put "Helps with database stuff" in the description and, to get rid of approval prompts, added allowed-tools: Bash(*). The result was mixed: when he said "add a column to the Book table" the skill didn't kick in, and the next day, when he asked why a SQL query was slow, Claude loaded the skill and created a migration nobody asked for.

What is your guess? Why did the skill trigger where it wasn't wanted rather than where it was? And what exactly does the allowed-tools: Bash(*) line do: does it limit the tools the skill can use, or something else? The first answer is in the context cost section, the second in the /add-migration section.

We covered the Copilot equivalent of skills in the sixth post of the GitHub Copilot series, and making prompts for code verifiable in the sixth post of the Prompt Engineering in Practice series. Here we focus on Claude Code's skill system: where skills live, when they load, how much context they cost and which repetitions they package in Bookstore.

Built-in Commands and Bundled Skills

The menu that opens when you type / at the prompt holds two kinds of things. Some are Claude Code's own commands, such as /clear, /compact, /permissions, /memory and /model. On the Commands page some entries carry a "Skill" label; these are the bundled skills that ship with Claude Code:

  • /code-review (alias /review): reviews the current diff, a pull request or a branch for correctness bugs; --fix applies the findings.
  • /simplify: reviews changed code for reuse, simplification and efficiency with four parallel agents and applies the fixes; it doesn't look for correctness bugs.
  • /batch: splits a large change into 5 to 30 independent units, presents a plan and, after approval, starts a background subagent for each unit in an isolated worktree.
  • /debug, /doctor: turns on and reads the session's debug log / checks installation, settings and CLAUDE.md problems.
  • /run, /verify: launches the app to see a change working / builds and runs it to confirm the change does what it should.
  • /loop, /claude-api: repeats a prompt at intervals while the session is open / loads the Claude API and Managed Agents reference.

In older Claude Code versions you wrote your own slash commands as Markdown files under .claude/commands/. According to the docs, custom commands have now been merged into skills: .claude/commands/add-migration.md keeps working and produces the same /add-migration command as .claude/skills/add-migration/SKILL.md. If both exist with the same name, the skill wins. Skills are recommended for new work, because only skills can carry reference files and scripts alongside them.

What a Skill Is and Where It Lives

A skill is a folder containing a SKILL.md file. The file starts with YAML frontmatter followed by Markdown instructions. Claude can load a skill on its own when it looks relevant, or you call it with /skill-name. The locations, and which one wins when two share a name:

LocationPathScope
Enterprise.claude/skills/<name>/SKILL.md in the managed settings folderMachines the organization deploys to; highest priority
Personal~/.claude/skills/<name>/SKILL.mdAll your projects on this machine; ahead of project
Project.claude/skills/<name>/SKILL.mdSessions in this repo
Nested<folder>/.claude/skills/<name>/SKILL.mdLoads when Claude reads or edits a file in that folder
Plugin<plugin>/skills/<name>/SKILL.mdAs /plugin-name:skill-name, without conflicts

The practical consequence of this order matters: if Emre has a personal copy at ~/.claude/skills/add-migration, the team's /add-migration in the repo never runs in his sessions. Don't keep personal copies of team skills. Project skills load from every parent directory from where you start Claude Code up to the repo root, so a session started inside web/bookstore-web still sees the skills at the root. Changes to skill folders are picked up without restarting the session. Skills enabled on your claude.ai account also come down to terminal sessions under ~/.claude/skills/synced/.

Context Cost: Why Move from CLAUDE.md to a Skill?

In post two we suggested moving multi-step procedures out of CLAUDE.md; the reason is how they load. CLAUDE.md is in context in full in every session. With skills, only the descriptions are listed at the start of a session; the full content loads only when the skill is invoked. Claude decides from the description whether a skill fits the job, so the description is both the trigger and a context cost.

Here is the answer to the first question. "Helps with database stuff" is both too broad and too vague: Claude may count a slow SQL query as "database stuff" and may not match "add a column to the Book table" with that sentence. The docs recommend writing what the skill does and when to use it in the description and putting the key use case first; the description and when_to_use fields together are cut at 1,536 characters in the listing. The definitive fix, though, for jobs with side effects such as migrations, is to stop the model from invoking the skill on its own at all: the description of a skill with disable-model-invocation: true never enters the context, and the skill runs only when you type /add-migration.

What happens after invocation? According to the docs, the rendered SKILL.md content enters the conversation as a single message and stays there on later turns; Claude Code doesn't re-read the file each turn. When compaction happens, the most recent invocation of each skill is re-attached after the summary, limited to the first 5,000 tokens of each and 25,000 tokens in total. In a long session where many skills were invoked, older ones can drop out. In the /skills list, the t key sorts skills by token count; that is where you see which ones are expensive.

Bookstore: /add-migration

Every migration in Bookstore needs the same steps: check the name, build, create the migration, generate and read the SQL, report data-loss risks, write the rollback note. That is a procedure, so it is a skill's job. .claude/skills/add-migration/SKILL.md:

---
name: add-migration
description: Create a new EF Core migration in Bookstore.Api, review the generated SQL and write rollback notes. Run only when the user invokes /add-migration.
argument-hint: "[MigrationName]"
arguments: [name]
disable-model-invocation: true
allowed-tools:
  - Bash(dotnet build Bookstore.slnx)
  - Bash(dotnet ef migrations list *)
  - Bash(dotnet ef migrations add *)
  - Bash(dotnet ef migrations script *)
  - PowerShell(dotnet build Bookstore.slnx)
  - PowerShell(dotnet ef migrations list *)
  - PowerShell(dotnet ef migrations add *)
  - PowerShell(dotnet ef migrations script *)
---

# Add an EF Core migration

Migration name: $name

1. If the name is empty or not PascalCase (for example `AddLowStockAlerts`), ask for a name and stop.
2. Build the solution: `dotnet build Bookstore.slnx`. Stop and report if the build fails.
3. Find the latest existing migration: `dotnet ef migrations list --project src/Bookstore.Api`.
4. Create the migration:
   `dotnet ef migrations add $name --project src/Bookstore.Api --output-dir Data/Migrations`
5. Generate the SQL from the previous migration to the new one:
   `dotnet ef migrations script <Previous> $name --project src/Bookstore.Api --output artifacts/migrations/$name.sql`
6. Read the migration and the SQL, then report:
   - tables and columns that are added, changed or dropped
   - anything that can lose data: dropped columns, narrowed types, NOT NULL without a default
   - indexes the new queries need that the migration does not create
7. Write `artifacts/migrations/$name.rollback.md` with the previous migration name and the command
   `dotnet ef database update <Previous> --project src/Bookstore.Api`.

Never apply the migration to a database and never edit an existing migration.

Let's read the fields one by one. arguments: [name] places the first argument into the content as $name; when you type /add-migration AddLowStockAlerts, every $name becomes that value. If the content had no placeholder at all, Claude Code would append what you typed as ARGUMENTS: <value> at the end of the content. argument-hint is just the hint shown in autocomplete. disable-model-invocation: true turns the skill into a command only you can call.

The answer to the second question is in allowed-tools. According to the docs this field doesn't restrict tools; it lets the listed tools run without asking you during the turn that invokes the skill, and the grant clears when you send your next message. Every other tool stays callable and your permission settings keep applying to them. So Emre's Bash(*) line meant approving every shell command without question for the turn the skill ran in. If you really want to restrict tools, there is a separate field: disallowed-tools. Our list has only the build and four dotnet ef subcommands, each for Bash and PowerShell; dotnet ef database update, which changes the database, is left out on purpose, and the ask rule from post three still makes it prompt.

Bookstore: angular-component

The second skill is not a procedure but a template: creating an Angular component that follows the project rules. We want Claude to use it on its own, but only while working on the Angular side. .claude/skills/angular-component/SKILL.md:

---
name: angular-component
description: Scaffold a standalone Angular component in web/bookstore-web that follows the project conventions (signals, OnPush, spec file). Use when creating a new component, page or dialog in the Angular client.
paths:
  - "web/bookstore-web/src/**"
---

# Angular component for bookstore-web

Create the component the user asked for. Before writing files, read the folder layout and naming rules in [reference.md](reference.md).

1. Put the files in the feature folder `web/bookstore-web/src/app/<feature>/`: `<name>.ts`, `<name>.html` and `<name>.spec.ts`.
2. The component is standalone, uses `ChangeDetectionStrategy.OnPush`, keeps its state in signals and gets data from a service in `src/app/core`.
3. Use the `input()` and `output()` functions, not decorators.
4. Write at least one test that renders the component with test data.
5. Run `npm test` in `web/bookstore-web` and show the test summary.

The reference.md in the same folder doesn't enter the context unless the skill is invoked; because SKILL.md mentions it with a link, Claude opens and reads it when needed. The docs recommend keeping SKILL.md under 500 lines and moving detailed reference material into files like this:

# bookstore-web layout

- `src/app/core/`: API services (`CatalogApi`, `OrdersApi`), interceptors and models shared by features
- `src/app/catalog/`, `src/app/cart/`, `src/app/checkout/`, `src/app/orders/`, `src/app/admin/`: one folder per feature
- Each feature exports its routes from `<feature>.routes.ts`; `app.routes.ts` lazy-loads them
- File names are kebab-case without a `.component` suffix: `low-stock-list.ts` holds the class `LowStockList`
- Shared test data lives in `src/testing/` (books: Dune, Emma, The Hobbit)

The paths field makes the skill load automatically only while matching files are being worked on; the patterns are relative to Claude's working directory. In a session working on the backend, Claude doesn't load this skill on its own, but you can always call it with /angular-component. This skill doesn't replace the Angular rule file from post two: the rule file carries short rules that apply to every .ts edit, while the skill carries the steps for creating a new component.

The Rest of the Frontmatter

context: fork runs the skill in a separate subagent; the agent field picks which subagent type to use (for example Explore for exploration). The forked subagent doesn't see the conversation history and gets the skill content as its task, so the instructions must make sense on their own. background only matters with fork and defaults to true; set it to false to wait for the result in the same turn. We cover subagents in detail in post six.

model and effort change the model and effort level for the rest of that turn and aren't saved to settings; picking a smaller model for a simple template job lowers cost. A command written into the skill content with ! and backticks is run before Claude sees the skill, and its output is placed into the content; for example, to add the current git diff to the skill. The shell field decides which shell runs these commands: bash by default, powershell for a team without Git for Windows.

Managing Access and Portability

Skills are tied to the permission system too. A Skill deny rule targets all skills, and rules such as Skill(add-migration) or Skill(deploy *) target individual ones. The skillOverrides setting can set a skill to "on", "name-only", "user-invocable-only" or "off"; that helps when you don't want the description of a plugin skill you didn't write to enter the context. disableBundledSkills turns off all bundled skills; but because /doctor prompt-audit from post two uses the bundled /claude-api skill, it goes off as well.

Claude Code skills follow the open Agent Skills standard. Portability has a limit, though: a skill uploaded to claude.ai or used through the Skills API accepts only the name, description, license, compatibility, metadata and allowed-tools fields; a Claude Code-specific field such as argument-hint causes an upload error, and running commands with ! doesn't work there. If you plan to use team skills in the Claude apps as well, keep a separate version in the format described in the skills post of the Working with Claude series.

In Production

Review skills like code. A skill can grant tool permissions for the turn it is invoked in and run commands with !; a skill added to the repo is a set of instructions that runs in everyone's session on the team. According to the docs, a project skill's allowed-tools and hooks are used even before the folder is marked as trusted; keep that in mind before running claude -p in a repo you don't trust.

Put disable-model-invocation: true on every skill with side effects: migrations, deploys, commits, messages to external services. Let the skills Claude runs on its own judgement be knowledge and template skills.

Watch the context budget. Every new skill's description takes up room in every session; sort the /skills list by token count and turn off the rarely used ones or set them to name-only.

Common Pitfalls

A vague description. "Helps with database stuff" either never triggers or triggers everywhere. Write what it does, when to use it and the key case in the first sentence.

Taking allowed-tools for a restriction. The field grants, it doesn't limit. A broad list means a broad family of commands that run unquestioned whenever the skill is invoked. For restrictions, use disallowed-tools.

Leaving workflows in CLAUDE.md. A procedure loaded needlessly in every session costs both context and adherence. Procedures go into skills, short rules into CLAUDE.md.

Keeping a personal copy. Because a personal skill takes precedence over the project skill, the skill your team updates never runs in your sessions.

Writing instructions for a forked skill that depend on the conversation. context: fork doesn't see the conversation history; "the endpoint we discussed above" means nothing to it.

A Small Experiment

★☆☆ Open the /skills list, sort it by token count with t and read the descriptions of the three most expensive skills. Which one is really worth listing in every session?

★★☆ Turn a repeated procedure in your own project into a skill with disable-model-invocation: true and remove it from CLAUDE.md. For a week, note how the room taken by memory files changes in the /context output.

★★★ Try the same skill with two descriptions: one vague, one in a "what it does + when" structure. Write five different requests (two that fit the skill, three that don't) and record, for each description, which requests made Claude load the skill.

Skills package instructions, but the work still happens in the main conversation's context. For helpers that produce a short report from long test output, review EF Core queries with read-only access or run in parallel in their own context window, the next step is subagents. In post six we add three subagents to Bookstore with .claude/agents/.