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.
- A skill is a folder with a
SKILL.md: a name, a description, and steps. You run it with/name, or the agent picks it from the description. - It lives in git, so a fix is one reviewed commit and every later session gets it.
- You add two:
commit, a checkpoint line every commit passes through, andquality-gate, which runs a script. Then you watch both refuse.
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:
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.
| Part | What it does |
|---|---|
name | The command you type, /commit. Defaults to the folder name. |
description | What 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. |
| Body | The steps, in order. It enters the conversation when the skill runs. |
| Other files | Anything 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
- Arguments.
/fix-issue 123puts123into$ARGUMENTS.$0,$1… pick arguments by position, and anarguments: [issue, branch]header lets the body say$issueand$branch. - Bundled scripts. A script in the skill's folder is reached as
${CLAUDE_SKILL_DIR}/scripts/render.sh, so it works wherever the skill is installed. A script gives the same result every run, which is whyquality-gatebelow carries one. - Live context. A line such as
- Changed files: !`git diff --name-only`runs before the agent sees the skill, and the output replaces it. If that command fails or is denied, the skill doesn't run at all.
Source: Skills verified 2026-10-01
Going deeper: who may start a skill, and what it may pre-approve
disable-model-invocation: true: only you can start it, with/name. The docs suggest it for side effects "like/commit". This course leaves it off so the agent can runcommitwhen a slice is done; the approval sits in step 6 and, from lesson 7, in anaskrule. Want the stricter form? Add the line.allowed-tools: tools that run without asking while the skill runs. It restricts nothing. A commit skill must never pre-approve its own approval step, so check this field in any skill you copy.- The listing cuts descriptions at 1,536 characters. Old
.claude/commands/files still work; commands "have been merged into skills".
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:
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.
---
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:
#!/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 Code | Codex | Antigravity | |
|---|---|---|---|
| Project skills | .claude/skills/<name>/, symlinks allowed | .agents/skills/<name>/, symlinks allowed | .agents/skills/<name>/ |
| Required header fields | None; description recommended | name and description | description |
| Run it yourself | /commit | $commit, or pick from /skills | /commit |
| Picked automatically | Yes, from the description | Yes, from the description | Yes, 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).
/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.
| When | Do this | Why |
|---|---|---|
| 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.
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 step | Backed in lesson 7 by |
|---|---|
| 6 · You approve | An ask permission rule on git commit |
| 2, 3 · No build output, no secrets | A git pre-commit hook, which runs for humans too |
| 5 · Subject format, no AI trailer | A git commit-msg hook |
| 8 · Never push | An 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.
- ---
- name: commit
- description: Helps with git stuff.
- disable-model-invocation: true
- allowed-tools: Bash(git commit *)
- ---
- 1. If nothing is staged, propose the files and ask before git add.
- 2. Refuse staged bin/, obj/, tmp/, .db or .env paths; name them and stop.
- 3. Draft the message from git diff --cached; wait for an explicit yes.
- 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
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).
- Start clean.
git statusshould say nothing to commit, working tree clean. - Check for a name clash. Run
ls ~/.claude/skills. If it listscommitorquality-gate, the personal one would win: move it out of that folder for this course, or rename both its folder and itsname:line (for example tomy-commit). - 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/
- Make them yours. Read both
SKILL.mdfiles in full. Incommit/SKILL.mdstep 4, replace the domain with your work email domain (or whichever domain you commit with).git config user.emailshould print an address on it. - 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. - 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.) - Run the gate. Type
/quality-gate. To the prompt forbash .agents/skills/quality-gate/gate.sh, answer Yes, not "Yes, and don't ask again": lesson 7 writes allow rules on purpose. Expectquality gate: green. - Break it on purpose. In your editor, add three spaces before
var builderinsrc/StockApi/Program.cs, save, and run/quality-gateagain. It must stop at step 1 withGATE FAILED at format(ours printederror WHITESPACE: … Delete 3 characters.) and never reach build. Let the agent rundotnet formatas its skill says; thengit diff src/StockApi/Program.csshould be empty. - Make /commit refuse. Force-stage a build file that
.gitignorenormally keeps out:dotnet build git add -f src/StockApi/obj/project.assets.json
Run/commit. It should stop at step 2, namesrc/StockApi/obj/project.assets.json, and proposegit restore --staged. Accept the restore;obj/is already in.gitignore, so no new entry is needed.git statusshould no longer list the file as staged. - 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 aschore(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 noCo-Authored-Byor "Generated with" line. - The handoff. Install only Matt Pocock's
handoffskill. 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-skillsstep 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 skillsand ask for the file's path. Read the file: does it state anything as done that wasn't? Start a new chat with/clearand 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.) - Point your rules at the skills. In
docs/ai/workflow.md, step 4 becomes "run thequality-gateskill" and step 6 "run thecommitskill; it drafts the message and waits for approval. Never push." Step 5 (review) stays manual until lesson 6. In the Definition of done inAGENTS.md, the format/build/test item becomes "thequality-gateskill passes" and the commit item names thecommitskill. 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.
- Both skills appear when you type
/ -
/quality-gatestopped atGATE FAILED at format, then went green -
/commitrefused theobj/file and named its path - A new chat picked up your work from a handoff file you had read first, without you re-explaining it
- The skills and
docs(ai)commits went through/commit: work email, no AI trailer
- mattpocock/skills: an open set of engineering skills (handoff, grilling, TDD, debugging), each with docs on when to reach for it. Pick the few you need.
- skill-creator: Drafts skills with you, tests them and tunes their descriptions.
- anthropics/skills: Anthropic's open collection of example skills to read and adapt.
- Agent Skills standard: The SKILL.md format that Claude Code, Codex and Antigravity share.
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.
Any multi-step instruction you give twice should become a skill. skill-creator can draft it and test it.
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.
"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."