Caelan's Domain

Claude Code Guide — Part 3: Build your first Skills

aiclaudeclaude-codecliskillsslash-commandsclaude-code-skills

Created: April 27, 2026 | Modified: April 27, 2026

What Skills are

You keep pasting the same five-line style guide into chat. You keep typing the same git commit ritual — read the diff, summarise it, write the message, stage, commit. You keep retyping the same review checklist on every PR. The model does the work fine each time; you do the orchestration of remembering what to ask for, in what order, with which constraints. That is the friction Skills fix.

A Skill is an on-disk procedure Claude Code loads when it is relevant. You name it, you write it once, it lives in your repo (or in your home directory if it is yours alone). Some Skills Claude reaches for automatically because the description matches what you are asking. Others sit on the shelf as /<name> slash commands you invoke when you want them. Either way, the prose lives in a file, not in your shoulders.

If you came up reading older Claude Code blog posts, you will have seen the term custom commands and a directory called .claude/commands/. That was the previous shape. Custom commands and Skills are now one primitive — both authored as SKILL.md files under .claude/skills/<name>/. The .claude/commands/ form is deprecated; new work goes in the unified directory. If you encounter .claude/commands/ in an existing repo, treat it as legacy and migrate when you next touch it.

This part hooks into Part 1's ## Skills, subagents, hooks (pointers) anchor — the stub you left at the bottom of CLAUDE.md. By the end of Part 3 you will have authored two Skills and added two pointer lines under that anchor. The .claude/settings.json you audited in Part 2 is the substrate underneath; Skills inherit those permissions unless their frontmatter narrows them.

A note on the word skills before we go further. Cowork — the web product — uses the same word for a related-but-different thing: saved prompts you build inside the web UI. Different surface, similar concept. Claude Code's Skills are SKILL.md files on disk that the CLI loads. Same word, two products. The teaching transfers; the artifacts do not.

Reference Skills vs. task Skills

Two shapes. The litmus test is one sentence: does invoking it have side effects?

A reference Skill is a convention pack. A style guide, a glossary of project terms, a "how we name branches" file, a "voice rules for prose in this repo" file. Claude reads it and writes accordingly. No commits, no shells, no edits to disk that the Skill itself drives. Claude decides when to apply it based on the Skill's description matching what you are doing — you do not invoke it by name. You write it once and forget it lives there.

A task Skill is a procedure with side effects. /commit reads the diff, drafts a message, stages files, runs git commit. /review checks out a PR, reads the diff, runs the linter, posts a summary. You invoke it as /<name> because side-effecting work should be deliberate, not auto-applied. Task Skills usually set disable-model-invocation: true so Claude does not silently fire them mid-conversation.

If you cannot decide which shape a piece of work is, ask whether the model running it without your asking would surprise you. If yes, it is a task Skill. If no, it is a reference Skill.

We will build one of each. The reference Skill is a house-style pack called house-style. The task Skill is commit. Pick names that match your work; the shapes are what carry over.

Author your first reference Skill

Reference Skills live at ./.claude/skills/<name>/SKILL.md for project scope (committed to the repo, shared with collaborators) or ~/.claude/skills/<name>/SKILL.md for user scope (yours alone, available in every project). For house style on a single repo, project scope is the right call.

mkdir -p .claude/skills/house-style

Now author the file. The frontmatter does the load-bearing work — description is the line Claude matches against to decide whether the Skill is relevant. Make it concrete. "Style guide" is too thin; the model has no way to know when "style guide" applies. Name the surfaces it covers and the verbs that should trigger it.

---
description: House style guide for prose in this repo — voice, vocabulary, formatting, and the cadences this codebase prefers in markdown articles, README sections, and PR descriptions. Apply when writing or editing any prose-heavy content.
paths:
  - "**/*.md"
  - "blog/**"
  - "knowledge_base/**"
---

# House style

Voice is declarative and warm. Second person. Short sentences mixed with
longer technical ones. No marketing language, no hedging openers ("It is
worth noting…", "Let me walk you through…"). Cut openers; start with the
point.

Vocabulary:
- "on-disk", not "local-first"
- "the model", not "the AI"
- "Claude Code", never "the assistant"

Formatting:
- Code blocks that represent named artifacts use a `title="..."` meta.
- Tables only when two or more axes are being crossed; otherwise use prose.
- Callouts (`> [!tip]`, `> [!warning]`) for short admonitions; do not wrap
  long content in them.

Two-item rhythm beats three when both items work. Resist the parallel
clause pile-up.

Two things to notice. The body is just prose — there is no template language, no special syntax. Claude reads the file the same way it reads any other context. The paths: glob is a scoping hint: when set, Claude only loads the Skill when the conversation touches files matching one of those globs. Without paths:, the Skill is eligible everywhere description matches.

Verify it loads. Restart your session (or just keep going — Skills are picked up on the next turn either way; we will come back to live change detection in a moment) and ask a question that should land on the Skill.

You: Draft a one-paragraph intro for a new article in /knowledge_base/
     about pnpm workspaces. The audience is working developers.

Claude: [Drafts a paragraph in declarative second-person voice, uses
        "on-disk" rather than "local-first", does not open with "Let
        me walk you through…", and skips the marketing register
        entirely. The Skill is being applied without you naming it.]

If the output ignores the rules, two things to check. The description is the only signal Claude has — if it is too generic, the Skill never triggers. Tighten it. Second, confirm you are inside a path that matches the paths: glob; the file house-style/SKILL.md only loads when one of the globs hits. A quick test: ask a coding question (paths: does not match) and watch the Skill stay silent.

Author your first task Skill

Task Skills are invoked, not auto-applied. /commit is the canonical example — every developer has the same five-step ritual and it is worth not retyping. We will build it.

mkdir -p .claude/skills/commit
---
description: Stage modified files, draft a conventional-commit message from the diff, and create the commit. Reads the working tree; writes one commit.
disable-model-invocation: true
user-invocable: true
allowed-tools: Bash(git status:*), Bash(git diff:*), Bash(git add:*), Bash(git commit:*)
arguments:
  - name: scope
    description: Optional commit-type override (feat, fix, chore, docs, refactor)
---

# /commit

Status before staging:

!`git status --short`

Diff to summarise:

!`git diff --staged`

If nothing is staged, also include the unstaged diff:

!`git diff`

Now do the following, in order:

1. Read the diff above.
2. Draft a conventional-commit subject line. If `$1` is set, use it as
   the type prefix (`$1: …`); otherwise infer the type from the change
   shape (feat / fix / chore / docs / refactor).
3. If files are unstaged but should be in this commit, stage them with
   `git add`. Do not stage files outside the modified set.
4. Run `git commit` with the drafted message.
5. Print the resulting `git log -1 --oneline` so I can see what landed.

Do not push. Do not amend. If the working tree is clean, say so and stop.

Several things are happening. disable-model-invocation: true tells Claude not to fire this Skill on its own — it only runs when you type /commit. user-invocable: true makes the slash command available in your prompt (the inverse — user-invocable: false — is for Skills you want to compose into other Skills but not expose to your fingers). allowed-tools narrows what the Skill is permitted to do regardless of how broad your settings are; the per-Skill allowlist is the floor, not the ceiling.

The ! prefix on those backticked lines is inline shell injection. Before Claude sees the Skill body, the CLI runs each !`command` and substitutes the output in place. By the time the model reads the prompt, !`git status --short` has already been replaced with the actual git status output. Claude sees the result, not the command. This is preprocessing, not tool use — the shell ran before the model did, and the model cannot decline or rewrite it.

The arguments: block declares a positional argument named scope. When you invoke /commit fix, the string fix becomes available as $1 (or $ARGUMENTS for the whole list). Reference it in the body with $0, $1, $2 for positional, $ARGUMENTS for the joined list, or by the declared name as $scope. Claude Code also exposes ${CLAUDE_SESSION_ID} (the current session UUID) and ${CLAUDE_SKILL_DIR} (the absolute path to this Skill's folder, useful when your Skill ships supporting files alongside SKILL.md).

Verify it works.

You: /commit fix

Claude: [Reads the staged diff, drafts "fix: handle empty config in
        loader", stages the modified files in scope, runs git commit,
        and prints the new oneline. Stops without pushing.]

If the slash command does not appear in autocomplete, check that user-invocable: true is set and that the file is named exactly SKILL.md inside .claude/skills/commit/. The directory name is what becomes the slash command — /commit comes from the folder, not the frontmatter.

The frontmatter that matters

Frontmatter is where Skills express their constraints. Most Skills only set two or three fields. The full list, with what each one buys you:

description is the only field Claude sees when deciding whether the Skill is relevant on a given turn. Be concrete. Name the verbs ("write", "edit", "review"), the surfaces ("markdown articles", "PR descriptions", "TypeScript components"), and the trigger phrase a user is likely to use. Generic descriptions ("style guide", "code helper") never fire.

disable-model-invocation (boolean, default false) blocks Claude from auto-applying the Skill. Set this to true on any task Skill — anything with side effects — so the slash command is the only way it runs.

user-invocable (boolean, default true) controls whether the Skill appears as a /<name> slash command. Set this to false for sub-Skills you want other Skills to call but do not want surfaced in your prompt autocomplete.

allowed-tools is the per-Skill permission floor. Same syntax as .claude/settings.jsonBash(git diff:*), Read, Write(./docs/**), etc. The list intersects with your settings: a Skill cannot grant itself a tool your settings deny, but it can narrow what the Skill is allowed to touch. Always narrow on task Skills.

paths (list of globs) scopes a Skill to specific file paths. When set, the Skill only loads when the conversation touches a matching file. Useful for keeping language-specific reference Skills out of unrelated work — a Python style guide should not load when you are editing the Next.js config.

arguments (list) declares positional arguments for slash invocation. Each entry has a name and a description. Reference them in the body as $0, $1, $2 (positional), $ARGUMENTS (the joined list), or by name as $scope, $path, etc. The substitutions are textual — they happen before Claude reads the prompt. ${CLAUDE_SESSION_ID} and ${CLAUDE_SKILL_DIR} are also available for any Skill that needs to know its own location or the current session.

model overrides the default model for this Skill only. model: haiku is the common move — cheap routine work runs on the small model and the rest of the session stays on whatever you set in settings.

effort (one of low, medium, high) tunes how much the model thinks before responding. Reference Skills usually leave this default; task Skills with checklist-style execution sometimes set low to keep them snappy.

context: fork spawns the Skill in an isolated context window — the Skill does not see your session transcript, and your session does not see the Skill's intermediate work. Only the final return shows up. This is the bridge into subagents and how it composes is exactly Part 4's territory.

Edits to a SKILL.md take effect on the next turn. You do not need to restart Claude Code, you do not need to reload anything — save the file, send a new message, the new content is in context. This makes iterating on a Skill fast: tighten the description, run the verification turn, see whether it triggers, edit again. The whole cycle is seconds.

When to put content in a Skill vs. CLAUDE.md vs. .claude/rules/

You now have three places content can live. The wrong placement is the most common mistake new users make — usually putting too much in CLAUDE.md and starving the on-demand surfaces. The disposition framework below is the test to apply.

Disposition framework — where does this content belong?
If the content is…Put it in…Why
Short, applies to every turn, never optional (stack, package manager, voice)CLAUDE.mdLoads on every session. Cost is per-line-per-turn — keep lean.
A reusable procedure with side effects (commit ritual, PR review, deploy check).claude/skills/<name>/SKILL.md (task Skill)Invoked deliberately as /<name>; only costs context when used.
A convention pack Claude should auto-apply when relevant (house style, naming rules, glossary).claude/skills/<name>/SKILL.md (reference Skill)Loads on-demand when description matches; scoped further with paths:.
A long, surface-specific rule that only applies to certain files (Python typing rules, SQL migration patterns).claude/rules/<name>.md with paths: glob frontmatterModular rule files scoped to file globs; ride along only on turns touching matching paths.
A permission decision (which Bash command to allow, which file pattern to deny).claude/settings.json — but you do not author it; you click "Always allow" in the prompt UI and Claude Code records it for you (Part 2)The file is the record of decisions you have already made under prompt pressure; you read it back to audit, not to author.
Something you have to type more than twice with the same shapeA Skill (reference if auto, task if invoked)Twice is the threshold. Three times is too many.
One-off context for a single taskThe chatNot everything needs to be saved.

The line between CLAUDE.md and .claude/rules/*.md is one of breadth: CLAUDE.md is project-wide and unconditional, .claude/rules/ files use a paths: glob in their own frontmatter and only load when the matching files are in scope. Long, surface-specific rules belong in .claude/rules/; the universal stuff stays in CLAUDE.md.

The Skills you authored above are now part of the routing menu the controller phase picks from when running the four-phase method — see Part 7. A task Skill is a doer with a known shape; that is exactly what control wants to hand work off to.

What just changed

You started this part with CLAUDE.md and .claude/settings.json configured. You finish with:

  • A reference Skill at .claude/skills/house-style/SKILL.md that Claude auto-applies whenever the description matches and you are inside a paths:-matched file.
  • A task Skill at .claude/skills/commit/SKILL.md that runs as /commit, narrowed to the git tools it needs, with inline shell injection feeding it the current diff.
  • The full frontmatter vocabulary — description, disable-model-invocation, user-invocable, allowed-tools, paths, arguments, model, effort, context: fork — and the substitutions you can reference in the body ($ARGUMENTS, $0/$1, $name, ${CLAUDE_SESSION_ID}, ${CLAUDE_SKILL_DIR}).
  • The disposition framework: short universals in CLAUDE.md, surface-specific rules in .claude/rules/*.md, repeatable procedures in .claude/skills/.
  • Two new pointer lines under Part 1's ## Skills, subagents, hooks (pointers) anchor — one naming house-style, one naming /commit — partially filling the stub. Part 4 fills the subagents half.

Live change detection means none of this required a CLI restart. The next turn picks up the new Skills.

What is next

Part 4 — Subagents picks up context: fork from § The frontmatter that matters and shows when a Skill should become a subagent — the deterministic boundary between "procedure I want auto-applied" and "worker I want isolated from my session". Part 5 — Hooks follows with the lifecycle handlers that fire on session events regardless of what the model decides. Together those two surfaces fill in the rest of Part 1's ## Skills, subagents, hooks (pointers) anchor.