Skip to main content
AcademytutorialClaude Skills tutorial series — Part 2: Writing your first skill

Claude Skills tutorial series — Part 2: Writing your first skill

Write a working Claude Skill from scratch — a mini-skill that summarises git statuses. Using /skill-creator as a starting point, a solid description, local testing, and adding it to your registry. Second of four short modules.

TutorialClaudeClaude CodeSkillsAITutorial series
15 min read

In part 1 you saw what a skill is. In this part you'll write one yourself: a working git-status-summary skill that produces a readable summary of the current working tree. At the end it sits in your ~/.claude/skills/ folder, you can invoke it via /git-status-summary, and you'll know how to share it with your team.

What we're building

A mini-skill that summarises your git status in a readable way — with categories (staged / unstaged / untracked), file counts, and a list of changes per category. Simple enough to write out completely in this part, but concrete enough to illustrate every step.

End result: type /git-status-summary in your session and you get something like:

Working tree status

Staged (3 files):
  • src/components/Button.vue — modified
  • src/components/Card.vue — modified
  • README.md — modified

Unstaged (2 files):
  • src/App.vue — modified
  • src/utils/format.ts — modified

Untracked (1 file):
  • notes.txt

Step 1 — Scaffold with /skill-creator

The easiest way to start a new skill is not to lay out folders yourself, but to let /skill-creator guide you. Open a Claude session and type:

/skill-creator

Claude then asks what you want to create. Answer roughly like this:

I want a skill git-status-summary that runs git status --porcelain, parses the output, and presents it in three groups (staged / unstaged / untracked) with filenames and change type. It should work on any Git repo.

/skill-creator will then:

  1. Ask for a short description — paste the carefully crafted version from below here.
  2. Ask whether the skill has side effects (no — read-only).
  3. Create a folder: ~/.claude/skills/git-status-summary/ with a starter SKILL.md.

If /skill-creator doesn't work for any reason, you can also just create the folder directly: mkdir -p ~/.claude/skills/git-status-summary && touch ~/.claude/skills/git-status-summary/SKILL.md. The skill is a plain text file.

Step 2 — Craft the description carefully

Recall from part 1: only the description is permanently loaded in the system prompt. This is where your auto-triggering stands or falls.

A good description for our skill:

description: Summarise the current Git working tree — groups changes by staged, unstaged, and untracked, and lists files per group with their change type

What's in there?

PieceWhy
SummariseAction verb up front. No "this skill is" or "helps with".
current Git working treeConcrete trigger words: someone who types "git status overview" or "what's changed" lands here.
groups changes by staged, unstaged, and untrackedTells Claude exactly what he gets — useful when choosing between several skills.
lists files per group with their change typeCloses with the observable result so a user immediately recognises whether this is what they want.

Write it in third person (the description is injected verbatim into the system prompt) and stay under ~250 characters — skill listings cut off after that.

Step 3 — Write the SKILL.md

Open ~/.claude/skills/git-status-summary/SKILL.md and fill it in like this:

---
name: git-status-summary
description: Summarise the current Git working tree — groups changes by staged, unstaged, and untracked, and lists files per group with their change type
metadata:
  category: Workflow
  tags: [git, workspace]
---

# Git Status Summary

Produces a clean, grouped summary of the current Git working tree.

**Input**: no arguments. Always reads `git status --porcelain` from the current
working directory.

**Steps**

1. **Verify a Git repo**

   Run `git rev-parse --is-inside-work-tree`. If it returns anything other than
   `true`, abort with: *"Not inside a Git repository — cd into one first."*

2. **Read the porcelain status**

   Run `git status --porcelain=v1` and split the output by line. Each line
   starts with a two-character XY status code:
   - `X` = index status, `Y` = working tree status
   - `M` = modified, `A` = added, `D` = deleted, `R` = renamed, `??` = untracked

3. **Group the lines**

   - **Staged**: lines where `X` is not blank and not `?`
   - **Unstaged**: lines where `Y` is not blank and the line is not untracked
   - **Untracked**: lines starting with `??`

   A single file can appear in both Staged and Unstaged (modified in both).
   Show it in both groups when that happens.

4. **Render the summary**

   Output in this exact shape (preserve formatting):

   ```
   Working tree status

   Staged (N files):
     • path/to/file — modified

   Unstaged (N files):
     • path/to/file — modified

   Untracked (N files):
     • path/to/file
   ```

   Omit any group that has zero files. If all three are empty, output:
   *"Working tree clean — nothing to commit."*

**Guardrails**

- Never run `git add`, `git commit`, `git reset`, or any other write command.
  This skill is read-only.
- Never call `git status` without `--porcelain` — the human-readable form is
  fragile across locales and Git versions.
- Never pass `--ignored` to `git status`. The "X is not blank and not `?`"
  staged-check would otherwise also match `!` (ignored) lines and produce
  a wrong staged group.
- Do not invent files. Only list what `git status --porcelain` actually returns.

Three things to note:

  1. Numbered steps — no prose paragraphs. One action per step, with a clear heading.
  2. A "Guardrails" section — what the skill explicitly may not do. For read-only skills, the "no write actions" rule is crucial.
  3. Concrete output format in a code block — Claude follows visible examples far more reliably than vague instructions.

How specific should your SKILL.md be? — degrees of freedom

A common mistake: giving every skill the same level of detail. In practice, your specificity should match the fragility of the task:

Kind of taskFreedomSkill style
Exploration, brainstorming, code reviewHighGive goals and guardrails, let Claude pick the approach
Feature work, refactorMediumGive steps with decision points, let Claude adapt
DB migrations, production deploys, CI configLowPrescribed commands, explicit confirmation gates

Our git-status-summary is read-only and mechanical — medium-to-low, with explicit formatting and hard guardrails. A hypothetical /explore-architecture would want high freedom instead ("think out loud, compare options, no fixed shape").

Dynamic content: injecting arguments and shell output

A last bit of syntax you'll run into later, but don't need yet for git-status-summary:

SyntaxMeaningExample
$ARGUMENTSEverything typed after /skill-name/app-create my-app$ARGUMENTS = "my-app"
$ARGUMENTS[0]First positional argumentFirst word after the skill name
${CLAUDE_SKILL_DIR}Absolute path to the skill folderFor references to bundled scripts
!`command`Shell output injected before the skill loads!`git branch --show-current` injects the current branch

The !`command` syntax in particular is powerful: it runs before the skill enters context, so you can pass runtime information (current branch, git user, date) to Claude as context. Not needed for our mini-skill, but worth knowing about for when you write more dynamic skills.

Asking the user something: AskUserQuestion

Some skills need input you can't derive from shell commands — for example a choice between two approaches, or confirmation before a destructive action. For that there is the AskUserQuestion tool: a built-in Claude Code tool that lets a skill, mid-execution, ask the user a short multiple-choice question and wait for the answer.

In your SKILL.md you simply instruct it in text:

3. **Confirm before applying**

   Use the `AskUserQuestion` tool to ask: *"Apply this migration to the
   production database?"* with options `Yes, apply now` and `No, abort`.
   Only continue to step 4 if the user picks the first option.

When to use it: at decision moments where Claude shouldn't choose autonomously (production deploys, branch strategy, sensitive refactors). When not: for info you can pull from git, gh or the file itself — then !`command` or a Read call is faster and less intrusive. There's no decision moment in git-status-summary, so we don't use it — but keep it in mind for your next, heavier skill.

Step 4 — Test locally

Before you share the skill: test it yourself. Three kinds of tests:

A. Manual trigger

Open a Claude session in a Git repo with some unstaged changes and type:

/git-status-summary

What you expect: a clean summary in the promised format. What you check: do the counts match? Is a file missing? Are staged + unstaged shown twice for mixed changes?

B. Auto-trigger checklist

The harder test: does it trigger automatically when it should, and stay quiet when it shouldn't?

PromptExpected behaviour
"What's changed in my working tree?"Should trigger
"Give me an overview of staged and unstaged"Should trigger
"Show me the diff for src/App.vue"Should not trigger (that's git diff, not status)
"Commit my changes"Should not trigger (write action, not status)
"What does git rebase do?"Should not trigger (informational question)

A prompt going wrong? Tweak the description and try again. Triggers over-eagerly? Add more specific words ("working tree", "staged/unstaged/untracked"). Triggers too rarely? Strip jargon and add natural terms ("git status overview", "what's changed").

C. Edge cases

  • Empty repo — no commits, no changes. Do you get the "Working tree clean" message?
  • Untracked onlyecho "test" > new.txt without add. Is only the Untracked group shown?
  • File with spacestouch "my file.txt" && git add "my file.txt". Is the name rendered correctly?

Step 5 — Global or per project?

Now that the skill works, the question is: where do you keep it?

For git-status-summary global is fine. For a fictional conduction-quality-check skill that runs the Conduction-specific PHPCS/Psalm/ESLint config, it belongs in .claude/skills/ of the project repo itself.

Step 6 — Add it to the skill registry

Within Conduction we keep a list of all internal skills (both personally useful and project-level). If you write a new skill that's valuable to the team:

  1. Add it to the relevant repo — usually <repo>/.claude/skills/<name>/ with a regular commit.
  2. Update the README.md of that .claude/skills/ folder if it exists — add a line with the name, a one-sentence description, and when you invoke it.
  3. Update the Hydra skill docs in ConductionNL/.github if the skill runs in a Hydra pipeline — the central documentation about which skills Hydra knows, what family they belong to, and what they do lives there. Without that update, the rest of the team (and later Hydra runs) won't know your skill exists.
  4. Open a PR and let one teammate trigger the skill in their own session. Don't review the description theoretically — literally type a prompt that ought to trigger it.

For global-personal skills (~/.claude/skills/) nothing needs to be committed — it's your own workshop.

Common beginner mistakes

SymptomCauseFix
Skill never triggers automaticallydescription is too vague or lacks trigger wordsRewrite in third person, action verb up front, concrete nouns from the user's question
Skill triggers too often on unrelated promptsdescription is too broad or overlaps with other skillsAdd specific scoping ("only for X, not Y")
/skill-name doesn't workname field in frontmatter differs from folder nameMake them identical (folder git-status-summaryname: git-status-summary)
Skill does something unexpectedly destructiveNo guardrailsAdd a Guardrails section with explicit "never do X" rules
Skill works on one laptop, not anotherHardcoded paths or OS-specific commandsUse relative paths; check OS-dependent commands (grep vs ggrep, etc.)

Test yourself

Four short questions to check you understood this part. Stuck? Click Hint. Curious about the answer? Click Answer.

1. How do you test a skill before sharing it with the team?

Hint

Three kinds of tests: a manual one, one for auto-triggering, and one for edge cases. What does each of them do?

Answer

Three levels:

  1. Manual trigger — type /skill-name in a session where the skill applies. Check that the output is correct and the format is tight.
  2. Auto-trigger checklist — come up with 5+ prompts that should trigger and 5+ that should not trigger. Walk through them one by one. Triggers too rarely? description too vague. Too often? description too broad.
  3. Edge cases — test situations that can predictably go wrong (empty input, special characters, missing dependencies). A skill that handles the happy path but misses the empty case isn't finished.

Only after all three do you share it — otherwise you discover problems in a teammate's session instead of your own.

2. Where would you put a skill for "fill in the Conduction-specific PR template" — global or per project?

Hint

The question is: who is this useful for? Just you, or the whole team?

Answer

Per project, in <repo>/.claude/skills/. Reason: it's Conduction-specific (a general Claude user wouldn't benefit) and you want the whole team — and Hydra's containers — to have the same version. Commit it to Git so it comes along with every fresh checkout.

Global (~/.claude/skills/) is for skills you find useful across all repos and that have nothing to do with the specific codebase. For example: a personal summarise-clipboard skill.

3. Why must the name in the frontmatter exactly match the folder name?

Hint

The name you type after / has to come from somewhere. What does Claude see first — the folder or the frontmatter field?

Answer

Claude finds skills by scanning your ~/.claude/skills/ and .claude/skills/ for folders containing a SKILL.md. For the slash trigger it looks at the name field in the frontmatter. If they don't match, there are two possible symptoms:

  • /foldername doesn't work but /namefield does — confusing, because your teammate only knows the folder name from Git.
  • Auto-trigger behaves unexpectedly — Claude links the description to the frontmatter name, not the folder name.

Rule of thumb: pick one name, use it everywhere identically. skill-creator keeps them in sync; with manual scaffolding you have to watch it yourself.

4. You have a skill that sometimes over-eagerly triggers on unrelated prompts. What's your first action?

Hint

Which field decides when Claude picks up a skill? And what tool do you have to validate that field?

Answer

Tweak the description. Concretely:

  1. Make it more specific — add trigger words that only fit the intended situation ("for the current Git working tree", not just "for changes").
  2. Add scoping — a short sentence like "Only for X, not Y" works surprisingly well.
  3. Re-test with your should-trigger / should-not-trigger checklist (see question 1).

Only once the description is rock-solid and it's still over-eager should you consider disable-model-invocation: true to turn auto-triggering off entirely and only allow /<name>. That's a last resort — usually a better description solves it.

Next step

Your skill works now. In part 3 you'll learn how to systematically measure whether it keeps working — with skill evals.