Skills, Hooks & Custom Commands
Last reviewed
AdvancedWhat you'll learn
~20 min- Author a SKILL.md that works across CLI tools
- Write a hook that enforces a guardrail the model can't forget
- Use permission allowlists as the middle path between approve-everything and YOLO
You’ve typed the same release-notes prompt 14 times. “Summarize the commits since last tag, group by feat/fix/chore, output markdown.” You paste it into Claude Code. Then Codex. Then Antigravity. And again next sprint. The copy-paste overhead is the tax you pay for not having a skill yet.
This lesson covers three infrastructure-layer mechanisms that make your setup durable:
- Skills — reusable workflows you invoke by name across tools
- Hooks — shell commands the harness runs at lifecycle events, with deterministic enforcement the model cannot override
- Allowlists — pre-approved permissions so you stop rubber-stamping the same safe commands
Skills: write once, invoke everywhere
The Agent Skills standard
The Agent Skills open standard (published December 2025, adopted by ~32 tools by March 2026) defines a minimal contract: a skill is a directory containing a SKILL.md file. That file has YAML frontmatter followed by markdown instructions. The agent reads the frontmatter to decide if the skill is relevant to the current task, then reads the body for step-by-step instructions — progressive disclosure by design.
Tools that adopted the standard by mid-2026 include Claude Code, Codex CLI, Cursor, VS Code Copilot, Antigravity CLI, JetBrains Junie, and Block Goose. The payoff: a skill you write today for Claude Code loads unchanged in Codex or Antigravity tomorrow.
Anatomy of a SKILL.md
Here is the release-notes skill, complete and realistic:
---name: release-notesdescription: > Generate structured release notes from git log since the last tag. Use when the user asks for release notes, a changelog, or "what changed since the last release."---
## Steps
1. Run `git describe --tags --abbrev=0` to get the most recent tag.2. Run `git log <tag>..HEAD --oneline --no-merges` to get commits since that tag.3. Group commits by conventional prefix: `feat:`, `fix:`, `chore:`, `docs:`, `refactor:`. - If a commit has no prefix, place it under "Other".4. Write the output as markdown: - `## [Unreleased]` heading - One sub-section per non-empty group (### Features, ### Bug Fixes, etc.) - Bullet each commit: `- <message> (<short-hash>)`5. Ask the user if they want the notes saved to `CHANGELOG.md` or printed only.
## Verification
After output, confirm the tag found in step 1 and the commit count match what`git log --oneline <tag>..HEAD | wc -l` returns.The frontmatter description is how the agent decides whether your skill is relevant before reading the body — it is the only signal used at matching time, so make it precise and include the phrases a user would actually say.
Where skills live per tool
| Tool | Project skills path |
|---|---|
| Claude Code | .claude/skills/<name>/SKILL.md (user-global: ~/.claude/skills/) |
| Antigravity CLI | .agents/skills/<name>/SKILL.md |
| Codex CLI, Cursor, others | Same SKILL.md format — check each tool’s docs for its discovery path |
In Claude Code, .claude/commands/release-notes.md and .claude/skills/release-notes/SKILL.md both create a /release-notes slash command — the two authoring styles are equivalent. The skills directory is preferred for cross-tool compatibility. Claude Code plugins (scaffolded with claude plugin init) bundle skills and auto-load them from .claude/skills/.
The cross-tool payoff
Commit your .claude/skills/ directory to the repo. Any teammate opening the project in Codex or Antigravity gets the same skill — no copy-paste of prompts, no “how did you phrase that?” questions, no drift between tools. The standard is the point.
Hooks: enforcement the model can’t forget
The determinism argument
CLAUDE.md gives the agent instructions. The agent follows them — until context runs long, a follow-up prompt shifts focus, or the model just reasons its way around a soft guideline. Instructions ask. Hooks enforce.
A hook is a shell command the harness runs at a lifecycle event (PreToolUse, PostToolUse, and others). It fires regardless of what the model decides. It cannot be prompted away.
The canonical example: this site’s build hook
This training site’s own .claude/settings.json runs the following:
{ "hooks": { "PostToolUse": [ { "matcher": "Edit|Write", "hooks": [ { "type": "command", "command": "npm run build 2>&1 | tail -5" } ] } ] }}The loop this creates:
- Agent edits a file (Edit or Write tool call fires)
- PostToolUse hook runs
npm run buildimmediately - The last 5 lines of build output appear in the agent’s context
- If the build fails, the agent sees the error on the next turn and fixes it before moving on
Without this hook, the agent might make three more edits before you discover the first one broke the build. With it, every edit is verified in real time — not because you asked, but because the harness guarantees it.
This pattern generalizes to any project with a fast verification command: a TypeScript typecheck, a test suite, a linter.
More hook ideas
These are directional — the point is the pattern, not the exact commands:
- Lint on edit:
"command": "eslint --fix $(git diff --name-only HEAD)"— keep the working tree clean without remembering to run it - Secret scan before commit: a PreToolUse hook on Bash commands matching
git committhat runsdetect-secrets scanortruffleHog— block the commit if secrets are found - Notify on stop: a hook on the session-end event that sends a desktop notification or a Slack message — useful for long autonomous runs you want to monitor without staring at the terminal
The JSON structure above is Claude Code’s format as of mid-2026. Hook event names, the matcher field, and configuration keys vary by tool. Antigravity and Codex CLI both have hook systems; check their current docs before porting. Inventing syntax here would be worse than unhelpful.
Allowlists: the middle path
Every command the agent wants to run stops for your approval — unless you’ve pre-approved it. Approving npm test for the fifteenth time is friction with no security value.
Permission allowlists in settings.json let you pre-approve commands you’ve already decided are safe. The agent runs them without prompting. Everything else still asks.
This is the middle path on the safety spectrum: not approve-everything, not full autonomy. For the full spectrum and when to use each position, see the Vibe Coding Reference — the spectrum is taught there and won’t be repeated here.
An illustrative example of the allowlist format in Claude Code’s settings.json (this repo’s actual settings.json contains only the hook above; verify current syntax in the Claude Code docs):
{ "permissions": { "allow": [ "Bash(npm test)", "Bash(npm run build)", "Bash(git status)", "Bash(git diff)", "Bash(git log*)" ] }}The pattern is: allow the commands whose output you want the agent to see and act on without interruption, and let the approval flow catch anything outside that envelope. Read-only commands and your own test suite are natural candidates. git push, rm -rf, and anything touching production credentials are not.
Add allowlist entries reactively — when you notice you’re approving the same command repeatedly. A short list you actually reviewed beats a long list you copy-pasted from a blog post.
You want Claude Code to always run 'npm test' after editing a file. Which mechanism is correct?
You write a release-notes skill in .claude/skills/release-notes/SKILL.md. A teammate opens the same repo in Codex CLI. What happens?
Which of these is the right job for a CLAUDE.md instruction vs. a hook vs. a skill?
Key takeaways
- A skill is a
SKILL.mdfile in a standard directory — YAML frontmatter for matching, markdown body for instructions. Write it once; it works across Claude Code, Codex, Antigravity, and other adopters of the Agent Skills standard. - Claude Code merges
.claude/commands/and.claude/skills/— both create slash commands. Prefer the skills directory for cross-tool portability. - Hooks enforce; CLAUDE.md asks. A PostToolUse hook on
Edit|Writeis the canonical pattern for build verification — this site uses it, and you should use it too. - Allowlists are the middle path — pre-approve safe commands to reduce friction without opening the full autonomy door.
- Hook and allowlist syntax is tool-specific. Verify against current docs before porting patterns across tools.
Next: Subagents and Orchestration — how to compose multiple agents into pipelines where each does only what it’s best at.