Stage 5 of 11

Skills: procedures the agent runs by name

A prompt you paste is a copy, and copies drift. A skill is the procedure kept as one file in the repo, which the agent loads by name and follows step by step, the same way in every session.

12 min read 45 min exercise Plaque: Skill Smith
In 30 seconds
Recall

Lesson 4: why is ai-session/PLAN.md committed on its own, before any code?

A plan that arrives in the same commit as the code, every box ticked, proves nothing about order. Committed alone and first, it shows the change was decided before it was built.

Lesson 3: dotnet test fails because the test provider can't run the code. What should the agent do?

A provider that can't run the code is a wall, not a bug in the agent's own work, so it hands over a 1-3-1 and waits. The quality-gate skill below repeats this rule.

What happened

Failed run

A developer wrote nine prompt templates before starting, a good idea, then pasted them into sessions by hand. They still named another project, ExamDomain and Angular. Nobody reviews a paste, so nobody caught it.

Other real sessions had no commit procedure at all. One commit carried 1,089 files, including a vendored tmp/ toolchain. Another committed a JWT signing key in appsettings.json.

Real sessions, anonymised

With the harness

The procedure is one file in the repo, .agents/skills/commit/SKILL.md. Typing /commit loads it. It refuses tmp/ and obj/, scans for secrets, and drafts the message from the diff.

A wrong step is fixed once, in a reviewed commit.

A pasted prompt vs a skill

Same words, kept two ways. What differs is who sees them change:

PASTED BY HAND A SKILL IN THE REPO notes/commit-prompt.txtCommit for StockApi; scan secrets. Session 1 · pasted by youCommit for StockApi; scan secrets.✓ Session 2 · pasted from an old copyCommit for ExamDomain (Angular).✕another project’s name, left in Session 3 · a teammate’s copyCommit for StockApi.✕the secret-scan line is gone three copies, three procedures stockapi/.agents/skills/commit/SKILL.mdversioned in git, cloned with the code,changed only through a reviewed diff /commitsession 1✓ /commitsession 2✓ /commitsession 3✓ same file, same steps, every run 1 commit fixes it

What a skill is

A skill is a folder holding a SKILL.md file: a short YAML header (the block between --- lines), then instructions in Markdown. Claude Code loads every skill's description at the start of a session, and the full body "only loads when invoked" (Skills). So ten skills cost you ten descriptions, not ten procedures.

PartWhat it does
nameThe command you type, /commit. Defaults to the folder name.
descriptionWhat it does and when to use it. The agent reads this to decide on its own when the skill applies, so the key use case goes first.
BodyThe steps, in order. It enters the conversation when the skill runs.
Other filesAnything else in the folder: reference notes the body links to, or a script the steps run. They load only when needed (the docs call this progressive disclosure).

Two ways a skill starts. You type /name, or the agent picks it because your request matches its description. Two header lines change that: disable-model-invocation: true means only you can start it (the docs suggest it for side effects such as deploy or commit), and user-invocable: false means only the agent can, for background knowledge that isn't a command (Skills).

Going deeper: arguments, bundled scripts and live context

Source: Skills verified 2026-10-01

Going deeper: who may start a skill, and what it may pre-approve

Source: Skills: frontmatter reference verified 2026-10-01

The commit skill: a checkpoint line

Think of commit as a line a staged change must pass through, gate by gate. Under each gate is what it turns away:

stagedchange 1See what’sstaged git add -Anever used✕ 2No buildoutput obj/, tmp/,bin/, .env✕ 3Nosecrets Key: “Sup…signing key✕ 4Workidentity a personaladdress✕ 5Messagefrom the diff “Update thechanges”✕ 6Youapprove no yes,no commit✕ 7Commit,never push git pushis yours✕ ✓onecommit Each gate stops the line and tells you what to do; none of them works around a failure.

Two terms. Conventional Commits is a format for commit subjects, type(scope): summary such as feat(products): add low-stock endpoint, so history reads as a list of changes (spec). gitleaks scans git for keys and passwords (install); until you install it in lesson 7, the skill runs the pattern check only.

The full commit SKILL.md, as you will adapt it

From reference-solution/.agents/skills/commit/. The one line you change is the domain in step 4.

.agents/skills/commit/SKILL.md
---
name: commit
description: Commit the current slice. Use whenever a slice has passed the quality gate and review and the work should be committed, or when the user says "commit". Drafts a Conventional Commit message from the staged diff, refuses secrets and build output, checks your work identity, and waits for human approval. Never pushes.
---

# Commit

Follow every step in order. If any check fails, stop and report; do not work around it.

1. **See what is staged.**
   `git status --short` and `git diff --cached --stat`.
   If nothing is staged, list the changed files, propose which belong to this slice, and ask the human
   before running `git add`. Never use `git add -A` or `git add .`.

2. **Refuse build output and local files.**
   `git diff --cached --name-only | grep -E '(^|/)(bin|obj|tmp)/|\.db$|(^|/)\.env$'`
   If anything matches: stop, list the paths, and propose `git restore --staged <path>` plus a `.gitignore` entry.

3. **Refuse secrets.**
   Run BOTH checks. gitleaks finds known key formats but misses readable config secrets (a JWT signing key
   spelled as a plain English phrase, for example), and the pattern catches those:
   - `git diff --cached -- . ':(exclude)ai-session' | grep -nEi '(key|secret|password|pwd|token)["'\'' ]*[:=] *["'\''][^"'\'' ]{8,}'`
   - `gitleaks git --pre-commit --staged --redact` (if installed; it also scans `ai-session/` transcripts)
   On any finding: stop, show the file and line (redacted), and explain where the value should live
   (environment variable, documented in `.env.example`). Continue only if the human confirms it is not a secret.

4. **Check identity.**
   `git config user.email` must be your work address (`@yourcompany.com`; change this to your own domain).
   If not, stop and ask the human to run `git config user.email <their work email>`. Do not set it yourself.

5. **Draft the message from the diff, not from memory.**
   Read `git diff --cached`. Write:
   - Subject: `type(scope): summary`, imperative, 72 characters or fewer.
     Types: feat, fix, docs, test, refactor, perf, build, ci, chore, style, revert.
     Scope: the area (products, orders, harness, plan …).
   - Body (optional): why, in 1–3 lines; mention any 1-3-1 decision the human made.
   - **No** `Co-Authored-By`, "Generated with" or any other AI attribution line.
   If the diff holds more than one slice, say so and propose splitting it instead of writing one message.

6. **Show and wait.** Print the staged file list and the full message. Ask: "Commit with this message?"
   Wait for an explicit yes. If the human edits the message, use their version verbatim.

7. **Commit.** `git commit -m "<subject>" -m "<body>"`. Report the short hash.

8. **Never push**, never amend a pushed commit, never rebase. Pushing is the human's job.

Notice the pattern: every refusal ends in an action for you ("propose git restore --staged", "ask the human to run git config"), so the agent has somewhere to go that isn't a workaround.

The quality gate: a skill that carries a script

"Test everything yourself" asks the model to remember the commands, their order, and to stop at the first failure. quality-gate puts that in a script:

.agents/skills/quality-gate/gate.sh
#!/usr/bin/env bash
# Quality gate: format -> build -> test, stop at the first failure.
set -euo pipefail
cd "$(dirname "$0")/../../.."   # the repo root: this script lives in .agents/skills/quality-gate/

step() { printf '\n== %s ==\n' "$1"; }

step "1/3 format (dotnet format --verify-no-changes)"
dotnet format --verify-no-changes || { echo "GATE FAILED at format: run 'dotnet format' and re-run"; exit 1; }

step "2/3 build (dotnet build -warnaserror)"
dotnet build -warnaserror --nologo -v q || { echo "GATE FAILED at build"; exit 1; }

step "3/3 test (dotnet test --no-build)"
dotnet test --no-build --nologo -v q || { echo "GATE FAILED at test"; exit 1; }

echo; echo "quality gate: green"

The SKILL.md beside it carries the judgement a script can't: fix the cause, never skip or weaken a test to go green, never bend production code to suit a test tool, and after two failed fixes, use 1-3-1. Your repo's .gitattributes already keeps *.sh files LF, so the script runs from a Windows checkout too.

One folder, three tools

Codex and Antigravity read skills from .agents/skills/<name>/. Claude Code reads .claude/skills/<name>/, and a skill entry there "can be a symlink to a directory elsewhere on disk". So the real folders live in .agents/skills/, and .claude/skills/ gets one symlink (a tiny file that points at another path) per skill. Git stores a symlink as that path, so it travels with the repo.

Gotcha: a personal skill with the same name wins. The docs' order is "Enterprise over personal, and personal over project". A ~/.claude/skills/commit from some other project replaces your repo's /commit, so none of its gates run. If you have used Claude Code for a while, check before you rely on yours.

Going deeper: the same skill in Codex and Antigravity
Claude CodeCodexAntigravity
Project skills.claude/skills/<name>/, symlinks allowed.agents/skills/<name>/, symlinks allowed.agents/skills/<name>/
Required header fieldsNone; description recommendedname and descriptiondescription
Run it yourself/commit$commit, or pick from /skills/commit
Picked automaticallyYes, from the descriptionYes, from the descriptionYes, from the description

Antigravity is retiring its older Workflows in favour of skills by 1 November 2026; /migrate-workflows converts them. Sources: Claude Code skills · Codex skills · Antigravity skills verified 2026-10-01

Let the agent write the next skill

You don't have to write every skill by hand. Anthropic's skill-creator plugin helps you draft a skill and then test it: it stores test prompts with the behaviour you expect, runs each in a clean subagent, grades the results, compares the skill against no skill, and tunes the description so the skill triggers when it should (Skills: creating skills with Claude's help).

in Claude Code
/plugin install skill-creator@claude-plugins-official
# then, for example:
evaluate my commit skill with skill-creator

A good habit: any multi-step instruction you type a second time is a skill waiting to be written. Ask skill-creator to draft it from what you just did, read the draft like any other diff, and commit it. The skills folder of the open anthropics/skills repo has more examples to learn from. plugin name verified 2026-10-01

When work has to leave the chat: handoff and fork

Lesson 1 gave you two ways to free the window: /compact for the same task, a new chat for a new one. Two more cover work that has to move. One is a skill, which is why it waits until now.

Context window
0% full
rules file (re-read from disk) rule typed in chat messages and tool results handoff file

WhenDo thisWhy
The work has to travel: a new chat tomorrow, another tool, another folder, a colleague/handoff, a skill (below), then a new chat: "Read <path> and continue"It writes what's in flight, why, and what's next to a file a fresh agent can read. Specs, plans and commits are linked by path, not copied
A side task, and you want to keep this chat as it is/fork (a copy runs in the background) or /branch (you switch into the copy; /resume returns)The copy gets your whole context; your own chat stays untouched verified 2026-10-01

/handoff isn't built in. It is a small, popular skill (user-invoked: disable-model-invocation: true, so the agent never runs it on its own) from Matt Pocock's open skills repo; he explains when to use it (and when /compact is the better move) in a short video, "/handoff is my new favourite skill". Two cautions from his docs: the file goes to your temp folder, so ask for the path and copy it somewhere durable if the next session isn't soon; and read it before you hand it on, because the next agent treats every claim in it as fact.

What would you do?

Now with all five moves. The deciding question is usually: does anything have to travel?

You stop for the day halfway through a slice. Tomorrow you'll carry on in Codex instead of Claude Code.

The work has to travel to another tool, which can't see this chat. A handoff file can travel; read it before you hand it over.

Mid-way through a plan, you want to test a different DTO shape on the side, without disturbing this chat.

/fork copies the whole conversation into a background session that works in its own worktree. Your chat stays exactly as it was.

Same tool, same folder, same slice, and you're carrying on now. The window is 85% full.

Nothing has to travel, so a file adds nothing. /compact with a focus keeps you in the task; most days this is the right move.

A colleague will finish your half-done slice on their machine this afternoon.

They need something they can read cold. Run /handoff, read the file, downgrade anything you only assumed, then send it. Plans and commits are linked by path, not copied.

The slice is committed and green. Your next task is an unrelated bug in another controller.

Nothing here needs to move, and nothing in it helps the next task. Start a new chat; /resume keeps the old one if you need it.

Harness it: tired of pasting the path? Write /pickup

After a few handoffs you'll notice the chore: copy the file's path, start a new chat, type "Read <path> and continue". And the new chat trusts that file blindly, even if someone committed since or you switched branch. That itch is how your own harness grows: whatever you keep typing, turn into a skill. Here, a /pickup that finds the newest handoff by itself, checks it against the repo, and waits for your go:

---
name: pickup
description: Continue earlier work from the newest handoff file. Use right after /handoff and a new chat.
argument-hint: "[optional: path to a handoff file]"
disable-model-invocation: true
---

1. Find the handoff. If $ARGUMENTS is a path, use it. Otherwise list the newest ones:
   `ls -t "${TMPDIR:-/tmp}"/*handoff*.md 2>/dev/null | head -3`
   Take the newest whose name or content matches this repo. If none, or it is over a day old, say so and ask.
2. Check it against the repo: `git branch --show-current`, `git status --short`, `git log --oneline -3`.
   Name every mismatch (other branch, new commits, files no longer changed).
3. Say which file you read, then list the next steps from it, one line each. End with "Confirm, or reorder."
4. Start only after the human confirms.

Save it as ~/.claude/skills/pickup/SKILL.md so it works in every repo. Now the loop is /handoff, a new chat, /pickup: no paths. Step 4 is the point: you see what it believes before it acts. Shape it to your habits; if you keep handoffs somewhere else, point step 1 there.

A skill is still a request

A skill is text the model reads. Specific steps, loaded at the right moment, make the right thing far more likely, but nothing blocks the agent from skipping step 3, or from committing without the skill. Lesson 7 adds what the tool enforces:

Skill stepBacked in lesson 7 by
6 · You approveAn ask permission rule on git commit
2, 3 · No build output, no secretsA git pre-commit hook, which runs for humans too
5 · Subject format, no AI trailerA git commit-msg hook
8 · Never pushAn ask rule on git push, and deny rules on force-push

Check yourself

Which part of a skill decides whether the agent picks it up on its own?

Only the name and description sit in context before the skill runs, and the agent matches your task against the description. That is why it says what the skill does and when to use it, key use case first.

You have ~/.claude/skills/commit from another project, and your repo has its own commit skill. What runs when you type /commit?

The order is enterprise, then personal, then project. Your repo's gates never run, so rename or move the personal skill.

The commit skill says "never push". Why does lesson 7 still add rules for git push?

A skill is text the model follows, so it raises the odds without blocking anything. Only the tool can refuse a command, which is what permission rules and git hooks do.

Practice first

Quick drills with instant feedback, before you touch the repo: apply what a skill is to files and situations you haven't seen yet.

Someone shares this commit skill. Click the lines that make it unsafe or unclear.

  1. ---
  2. name: commit
  3. description: Helps with git stuff.
  4. disable-model-invocation: true
  5. allowed-tools: Bash(git commit *)
  6. ---
  7. 1. If nothing is staged, propose the files and ask before git add.
  8. 2. Refuse staged bin/, obj/, tmp/, .db or .env paths; name them and stop.
  9. 3. Draft the message from git diff --cached; wait for an explicit yes.
  10. 4. Commit, then git push so the team sees it.

What would you do?

You write a /deploy-staging skill. The agent must never start it on its own; only you, by typing the command. What do you change?

disable-model-invocation: true means only you can start it, the form the docs suggest for side effects. user-invocable: false does the opposite, and allowed-tools only pre-approves commands.

Your new skill must run format, then build, then test, stopping at the first failure, the same way every run. Where do those commands belong?

A script gives the same result every run, which is why quality-gate carries gate.sh. The SKILL.md beside it keeps the judgement a script can't: fix the cause, never weaken a test.

A teammate says /commit let a tmp/ folder through on their machine, though the repo's commit skill refuses tmp/. What do you check first?

Personal beats project: a ~/.claude/skills/commit from another project replaces the repo's /commit, so none of its gates run. Rename or move it.

Exercise: two skills, two refusals, one commit through the skill

Exercise · about 45 min · you add: the commit and quality-gate skills, and the workflow pointing at them

Work in your practice repo (~/projects/stockapi), with this course's repo cloned beside it (~/projects/agent-harness-course). Windows: use WSL2 or Git Bash (setup).

  1. Start clean. git status should say nothing to commit, working tree clean.
  2. Check for a name clash. Run ls ~/.claude/skills. If it lists commit or quality-gate, the personal one would win: move it out of that folder for this course, or rename both its folder and its name: line (for example to my-commit).
  3. Copy the two skills. From the repo root:
    mkdir -p .agents/skills
    cp -r ../agent-harness-course/reference-solution/.agents/skills/commit .agents/skills/
    cp -r ../agent-harness-course/reference-solution/.agents/skills/quality-gate .agents/skills/
  4. Make them yours. Read both SKILL.md files in full. In commit/SKILL.md step 4, replace the domain with your work email domain (or whichever domain you commit with). git config user.email should print an address on it.
  5. Point Claude Code at them. One symlink per skill:
    mkdir -p .claude/skills
    ln -s ../../.agents/skills/commit .claude/skills/commit
    ln -s ../../.agents/skills/quality-gate .claude/skills/quality-gate
    ls -l .claude/skills
    Each line should show ->; the target is relative to the link's folder, hence ../../. Git Bash: without Developer Mode, copy instead (cp -r .agents/skills/commit .agents/skills/quality-gate .claude/skills/) and re-copy after every edit.
  6. See them. Start a fresh session in Manual mode, claude --permission-mode default, and type /: both skills should be listed. (Session already open? Run /reload-skills.)
  7. Run the gate. Type /quality-gate. To the prompt for bash .agents/skills/quality-gate/gate.sh, answer Yes, not "Yes, and don't ask again": lesson 7 writes allow rules on purpose. Expect quality gate: green.
  8. Break it on purpose. In your editor, add three spaces before var builder in src/StockApi/Program.cs, save, and run /quality-gate again. It must stop at step 1 with GATE FAILED at format (ours printed error WHITESPACE: … Delete 3 characters.) and never reach build. Let the agent run dotnet format as its skill says; then git diff src/StockApi/Program.cs should be empty.
  9. Make /commit refuse. Force-stage a build file that .gitignore normally keeps out:
    dotnet build
    git add -f src/StockApi/obj/project.assets.json
    Run /commit. It should stop at step 2, name src/StockApi/obj/project.assets.json, and propose git restore --staged. Accept the restore; obj/ is already in .gitignore, so no new entry is needed. git status should no longer list the file as staged.
  10. Commit the skills through the skill. With nothing staged, run /commit. It should propose the new files under .agents/skills/ and .claude/skills/ and ask before staging; say yes. Read the drafted message (such as chore(harness): add commit and quality-gate skills) and approve it. Then:
    git log -1 --format='%h %ae%n%B'
    Your work email, a conventional subject, and no Co-Authored-By or "Generated with" line.
  11. The handoff. Install only Matt Pocock's handoff skill. A skill is just a folder, so its one file is the whole install, and putting it in your user folder makes it work in every repo:
    mkdir -p ~/.claude/skills/handoff
    curl -fsSL https://raw.githubusercontent.com/mattpocock/skills/main/skills/productivity/handoff/SKILL.md -o ~/.claude/skills/handoff/SKILL.md
    Read the file before you use it. (Skip the repo's plugin and its /setup-matt-pocock-skills step for now: they add about 27 skills and write a section into your AGENTS.md.) Start a new chat so it loads, then right after that commit, run /handoff next: step 9 of lesson 5, point docs/ai/workflow.md and the Definition of done in AGENTS.md at the two skills and ask for the file's path. Read the file: does it state anything as done that wasn't? Start a new chat with /clear and say "Read <path> and continue." Pass: it starts on step 9 (both files) without asking you to re-explain the repo. Then finish step 9 in this new chat. (No plugin installs allowed where you are? Ask the agent to write the same kind of handoff file, and skip the install.)
  12. Point your rules at the skills. In docs/ai/workflow.md, step 4 becomes "run the quality-gate skill" and step 6 "run the commit skill; it drafts the message and waits for approval. Never push." Step 5 (review) stays manual until lesson 6. In the Definition of done in AGENTS.md, the format/build/test item becomes "the quality-gate skill passes" and the commit item names the commit skill. Commit both through /commit: docs(ai): point workflow at the skills.

Codex: it reads .agents/skills/ as they are, so skip the symlinks; type $quality-gate or $commit, and check the first commit message for any attribution line your Codex setup adds. Antigravity: the same folder works; type /quality-gate or /commit.

Done when: tick all five to earn the Skill Smith plaque
Worth installing or reading

Each link has its own install steps. Install only what you will use, check how recent it is, and read what a plugin adds before you enable it. Links checked 2026-10-01.

Harness it

Any multi-step instruction you give twice should become a skill. skill-creator can draft it and test it.

Read next

Extend Claude with skills, especially "Control who invokes a skill". For the shared .agents/skills layout, Codex: build skills. The commit skill checklist has the gates on one page.

Ask your agent

"Read my .agents/skills/commit/SKILL.md. Which step are you most likely to skip in a long session, and what in this repo would notice if you did? Don't edit anything."