Building and Publishing Your Own Skills
A custom agent skill is a folder with a SKILL.md file: YAML frontmatter with a name and a description, followed by instructions that Claude Code, Codex and Cursor load when a task matches the description. To build one: scaffold it, write a description that triggers precisely, validate the format, and prove with an eval that it beats a no-skill baseline.
Your team never edits a migration that has already shipped, numbers new ones sequentially, and backfills every NOT NULL column. That rule lives in two senior engineers’ heads, so last week an agent “fixed a typo” in 003_init.sql and the deploy failed. This page is for the developer who turns that rule into a skill and for the tech lead who owns the shared skills repository and needs proof that each skill works before eight people install it.
What you’ll walk away with from building a custom skill
Section titled “What you’ll walk away with from building a custom skill”- A complete, spec-conformant
db-migrationskill:SKILL.md, a review checklist inreferences/and a helper script - The scaffolding command for each route:
npx skills init,claude plugin init, and theskill-creatorskill in Claude Code, Codex and Cursor - A verification routine that replaces “read the skill and hope”:
claude plugin validate --strict, the reference validator,claude plugin evalagainst a no-skill baseline, and a context-cost reading - Publishing and pinning commands for public and private repositories, plus the CI job and sign-off rule that keep the skill honest
Commands marked as run were run against Claude Code 2.1.283 (the latest channel on 2026-09-26), codex-cli 0.157.1 and the skills CLI 1.7.0 in a scratch home directory. Cursor has no CLI in the test environment, so its steps come from the skills CLI’s agent table and are not run.
Do you need a skill, a rules file, a plugin or an MCP server?
Section titled “Do you need a skill, a rules file, a plugin or an MCP server?”A skill is the right unit for a procedure the agent needs only sometimes. Its name and description cost tokens in every session; the body loads only when a task matches. Pick by what you are packaging:
| Your situation | Build |
|---|---|
A rule that applies to every task in the repository (“use pnpm”, “never commit .env”) | A line in AGENTS.md or CLAUDE.md, not a skill |
| A multi-step procedure for one kind of task (migrations, releases, incident notes) | A skill |
| A procedure that only works with a hook or MCP server beside it | A plugin that bundles the skill |
| A live connection to an internal API or database | An MCP server |
Always-on rules belong in the context files covered in optimizing AGENTS.md for Codex and the Claude Code memory system. Moving them into a skill makes them conditional, and the agent then skips them on the tasks where the description does not match.
What goes into a skill folder?
Section titled “What goes into a skill folder?”The Agent Skills specification requires one file, SKILL.md, in a folder whose name matches the skill’s name. Everything else is optional and loads on demand.
acme-agent-skills/ # the team's skills repository├── .claude-plugin/plugin.json # optional: lets Claude Code validate, eval and measure it├── skills/│ └── db-migration/ # folder name == frontmatter name│ ├── SKILL.md # required: frontmatter + instructions│ ├── references/checklist.md # loaded only when SKILL.md points the agent at it│ └── scripts/next-number.sh # run by the agent, never loaded into context├── evals/ # eval cases for claude plugin eval└── README.md # for humans: what each skill does, install lineThe frontmatter rules come from the specification; the reference validator in the next section rejects a file that breaks them:
| Field | Required | Rule |
|---|---|---|
name | Yes | 1–64 characters, lowercase letters, digits and single hyphens; no leading or trailing hyphen; must match the folder name |
description | Yes | 1–1,024 characters; says what the skill does and when to use it |
license, compatibility, metadata | No | compatibility is at most 500 characters and states environment needs (“Requires git and jq”) |
allowed-tools | No | Experimental, space-separated pre-approved tools; support varies by agent |
The specification also sets the budget. About 100 tokens of name and description load at startup for every installed skill. The body loads on activation, with fewer than 5,000 tokens recommended and SKILL.md kept under 500 lines. Files in references/, scripts/ and assets/ load only when needed, so move long material there and link it one level deep from SKILL.md.
Here is the finished skill. The code fences inside it are part of the file.
---name: db-migrationdescription: Creates and reviews SQL schema migrations in migrations/. Use when the user asks to add, drop, rename or alter a table, column or index, or says "write a migration". Do not use for seed data, ad-hoc queries or editing application code.---
# Database migrations
## Rules
1. Never edit a file in `migrations/` that exists on the default branch. Applied migrations are immutable: write a new migration that corrects the old one.2. Name the file `NNN_short_description.sql`. Get NNN by running `scripts/next-number.sh` from the repository root. Do not guess the number.3. One schema change per file.4. A new `NOT NULL` column needs a `DEFAULT` or a backfill `UPDATE` in the same file.5. After writing the file, run `npm run db:migrate -- --dry-run` and include its output in your reply.
## Before you finish
Go through [the review checklist](references/checklist.md) and state which items you checked.
## Example
Request: "Add a last_login_at timestamp to users."Existing files end at `013_add_orders_status.sql`.Result: a new file `migrations/014_add_users_last_login_at.sql` containing
```sqlALTER TABLE users ADD COLUMN last_login_at TEXT;```
and no change to any existing migration.The checklist that SKILL.md links to keeps the review items out of the body until the agent reaches the last step:
# Migration review checklist
- [ ] No file that exists on the default branch was modified- [ ] The new file number is the output of scripts/next-number.sh- [ ] The file contains exactly one schema change- [ ] Every new NOT NULL column has a DEFAULT or a backfill UPDATE- [ ] The dry-run output is included in the replyThe helper script turns rule 2 from a judgement into a command, which is why the rule tells the agent not to guess:
#!/usr/bin/env bash# skills/db-migration/scripts/next-number.sh — prints the next migration numberset -euo pipefaillast=$(ls migrations/ | grep -E '^[0-9]{3}_' | sort | tail -1 | cut -c1-3)printf '%03d\n' $((10#${last:-0} + 1))Write a description the agent actually triggers on
Section titled “Write a description the agent actually triggers on”The description is the only part of the skill the agent sees before it decides to load it. Write it as a matching rule, not a summary:
- Name the trigger phrases and objects. “add, drop, rename or alter a table, column or index” matches how people ask. “Helps with databases” matches nothing reliably.
- State the exclusions. “Do not use for seed data” stops the skill firing on neighbouring tasks and spending its body’s tokens there.
- Put the verb first. “Creates and reviews SQL schema migrations” tells the agent what it gets.
In the body, write rules the agent can check, one per line, with the reason where the rule is not obvious. “Follow best practices for migrations” is not checkable. “Never edit a file that exists on the default branch” is.
Scaffold the skill in your tool
Section titled “Scaffold the skill in your tool”Three routes produce the same folder. npx skills init writes a bare SKILL.md for any agent. claude plugin init writes a personal Claude Code skill that Claude Code can validate, evaluate and measure. The skill-creator skill interviews you and drafts the content.
The portable route works the same everywhere (run):
cd acme-agent-skills/skillsnpx skills init db-migration # creates db-migration/SKILL.md with a TODO descriptionFor a personal skill, scaffold a skills-dir plugin (run):
claude plugin init db-migration --description "Schema migration rules" --author "Acme Platform Team"# √ Created plugin "db-migration" at ~/.claude/skills/db-migration# It will auto-load next session as db-migration@skills-dir. Run /reload-plugins to load it now.It writes ~/.claude/skills/db-migration/SKILL.md and .claude-plugin/plugin.json. Add --with skills agents hooks to scaffold more component folders. For a project skill, put the folder in .claude/skills/db-migration/ and commit it.
To have the agent draft and test the skill, install Anthropic’s skill-creator. It writes the skill, runs test prompts with and without it, benchmarks the results and tunes the description for triggering accuracy.
claude plugin install skill-creator@claude-plugins-officialcodex-cli 0.157.1 bundles a system skill named skill-creator, so nothing needs installing. Invoke it in a session with $skill-creator. It is OpenAI’s own skill, not a copy of Anthropic’s: it scaffolds with scripts/init_skill.py and checks the result with scripts/quick_validate.py. By default it writes to ${CODEX_HOME:-$HOME/.codex}/skills, so name the target when you want a repository skill:
$skill-creator Create a skill named db-migration in .agents/skills/ from the rules in docs/migrations.md.Codex reads project skills from .agents/skills/. codex-cli 0.157.1 has no command that evaluates a skill; test it with the Claude Code eval below or with a codex exec smoke run.
This page could not confirm a bundled skill creator in Cursor (its skills documentation, last checked 2026-08-28, names none). Install Anthropic’s skill-creator with the skills CLI, which writes to .agents/skills/ for Cursor:
npx skills add anthropics/skills --skill skill-creator -a cursorThen ask the agent in chat to use skill-creator to create db-migration. The skill’s with-and-without comparison expects subagents, and its description-optimisation step runs claude -p, so run the behaviour check in the next section from Claude Code and the Cursor smoke test by hand.
As of 2026-09-26, Anthropic’s skill-creator shows 391,071 all-time installs on skills.sh, according to the third-party LinklyAI/best-skills scrape of that date (SECONDARY; skills.sh itself was not reachable). Its source repository, anthropics/skills, had 178,380 GitHub stars on the same date.
Prove the skill works before anyone installs it
Section titled “Prove the skill works before anyone installs it”Nobody should have to read a skill line by line to trust it. The proof rests on format, behaviour against a baseline, context cost and a smoke run in each tool.
-
Validate the format with two validators.
claude plugin validate --strictchecks the manifest and every skill’s frontmatter and exits1on any warning. The reference validator from the specification’sskills-reflibrary checks the naming rules that Claude Code tolerates (run):./acme-agent-skills/skills/db-migration claude plugin validate --strict ./acme-agent-skillsuvx --from skills-ref==0.1.1 agentskills validate ./acme-agent-skills/skills/db-migrationOn 2.1.283,
validate --strictfailed a skill with no description and a file with no frontmatter, but passedname: Bad_Name. The reference validator rejected it: “Skill name ‘Bad_Name’ must be lowercase”. Theskills-refREADME calls the library “intended for demonstration purposes only”, so use it as a lint, not a gate you depend on. -
Give Claude Code a manifest to evaluate.
claude plugin evalruns cases with the skill and without it and reports the difference. It needs a plugin, so the repository carries a minimal.claude-plugin/plugin.json:{"name": "acme-agent-skills","version": "0.1.0","description": "Acme platform team skills","author": { "name": "Acme Platform Team" },"skills": ["./skills/"]}Point
skillsat./skills/, not./. A freshclaude plugin initscaffold declares"skills": ["./"], andclaude plugin eval initthen refuses to write becauseevals/would overlap a component path. -
Write the eval cases.
claude plugin eval init --bare new-columncreatesevals/new-column/prompt.md(the task,max_turns,allowed_tools) andevals/new-column/graders/criteria.md(an LLM-graded definition of success). Fill in a case that must trigger the skill:---max_turns: 10allowed_tools: [Read, Glob, Grep, Skill]---The migrations/ directory contains 001_init.sql through 013_add_orders_status.sql.Add a last_login_at timestamp column to the users table. Show the file you would create.---type: llmweight: 1---The response proposes a new file named 014_*.sql containing one ALTER TABLE statement,and does not propose any change to an existing migration.The case checks only the file the agent proposes, so
allowed_toolsdeliberately withholds Bash and the agent cannot follow rules 2 and 5 during the eval. To exercise the numbering script as well, keep Bash in the case and grant it on the eval command with--allow-tools Bash, which runs real shell commands on your machine.Add a second case with an unrelated request (“rename the heading in README.md”) whose criteria say that no migration file is proposed. It catches a description that fires too broadly.
-
Run the eval against the baseline. The eval calls a model on your own credentials, so run it locally or in a separate job that has a key:
Terminal window claude plugin eval ./acme-agent-skills --runs 3 --threshold 0.8 --no-publishThe command exits
1when any case scores below the threshold. The default--ablation with-withoutadds the no-skill arm. A skill that scores no better than its baseline is dead weight. Add a with-only gradertool_used: Skillso the report shows whether the skill fired, separately from the score;claude plugin eval --helpin 2.1.286 lists such graders as a plugin-fired indicator underwith-without. Eval runs the plugin on your machine as you, so pass--trust-pluginonly for skills you wrote. -
Read the context cost. Load the checkout with
--plugin-dirand read what the skill adds to every session and what it costs when it fires. Without--plugin-dir,plugin detailslooks only at installed plugins and reportsPlugin "acme-agent-skills" not found(run):Terminal window claude --plugin-dir ./acme-agent-skills plugin details acme-agent-skills# Source: acme-agent-skills@inline# Projected token cost# Always-on: ~98 tok added to every sessionThe figure depends on the description: ~98 tokens is the
db-migrationdescription above. A description that grows past a few sentences shows up here first. -
Smoke-test in each tool you support. Install the working copy into Codex and Cursor, load it into Claude Code with
--plugin-dir, and run the positive prompt once in each. Without--plugin-dir,claude -pruns without the skill and can pass on the baseline alone:Terminal window npx skills add ./acme-agent-skills --skill db-migration -a codex -a cursor -ycodex exec --sandbox read-only "Add a last_login_at column to users. Show the migration file you would create."claude -p --plugin-dir ./acme-agent-skills --permission-mode plan "Add a last_login_at column to users. Show the migration file you would create."In Cursor, paste the same prompt into Agent chat. Pass when each tool proposes
014_…and leaves existing files alone.
Who signs off: the skill’s owner, named in CODEOWNERS for skills/db-migration/, approves a change only when the validators pass and the eval delta has not dropped. Reviewers read the rules diff, the eval scores and the cost line, not the agent’s output from every run.
Publish, pin and roll out the skill
Section titled “Publish, pin and roll out the skill”A pushed public repository is installable at once; nothing needs approving:
npx skills add acme/agent-skills --skill db-migration -a claude-code -a codex -a cursorPin a release. Tag the repository and install from the tag with a # fragment. In skills 1.7.0, acme/agent-skills@v1.2.0 does something else: the part after @ is read as a skill name.
git tag v1.2.0 && git push origin v1.2.0npx skills add acme/agent-skills#v1.2.0 --skill db-migration -a claude-codePrivate repositories use the same command. The CLI tries your git credentials, then an authenticated GitHub CLI, then SSH, and also accepts GitLab, Azure DevOps and any HTTPS or SSH git URL. The CLI’s anonymous install telemetry sends repository and skill identifiers only for repositories GitHub confirms are public, but for other hosts it may send them, so set DISABLE_TELEMETRY=1 when you install from an internal git server.
One repository only? Commit the folder to .claude/skills/ and .agents/skills/ and skip the CLI. The whole organisation? Wrap the skills in a plugin and ship it from a private team marketplace, which adds versioned updates and managed settings.
Discovery. npx skills find searches the skills.sh index. How skills.sh selects and ranks repositories could not be checked on 2026-09-26, because skills.sh was not reachable from the test environment; check skills.sh itself before promising a listing.
How a skill change moves through the delivery loop
Section titled “How a skill change moves through the delivery loop”Treat a skill as code with its own tests. The loop that keeps it honest:
-
Change the convention and the skill in one pull request. When the team switches migrations to a new format, the same PR edits
SKILL.mdand adds an eval case for the new rule. -
CI runs the format gate.
claude plugin validate --strictneeds no model and no API key, so it runs on every PR:.github/workflows/skills.yml name: skillson: pull_requestpermissions:contents: readjobs:validate:runs-on: ubuntu-lateststeps:- uses: actions/checkout@v7 # pin to a full commit SHA in your repositorywith:persist-credentials: false- run: npx -y @anthropic-ai/claude-code@2.1.283 plugin validate --strict .- run: pipx run --spec skills-ref==0.1.1 agentskills validate skills/db-migration -
The owner runs the eval before merge and pastes the with-skill and without-skill scores into the PR.
-
Tag the release after merge, and move teammates to it with
npx skills updateor a new pinned install.
When a custom skill doesn’t fire or misbehaves
Section titled “When a custom skill doesn’t fire or misbehaves”The skill never loads. The description does not match how people phrase the task. Run the eval’s unrelated and positive cases; if the positive case scores the same with and without the skill, rewrite the description with the actual verbs from your team’s requests, then rerun. Anthropic’s skill-creator has a description-optimisation step for exactly this.
The skill fires on unrelated tasks. The description is too broad. Add a “Do not use for…” sentence and an eval case for the neighbouring task it wrongly caught.
The agent follows the example but breaks the rule. Agents copy examples literally. Check that every example in SKILL.md obeys every rule, and move judgement calls into a script, as next-number.sh does for numbering.
It works in Claude Code but not in Codex or Cursor. The files are in the wrong directory. Project installs go to .claude/skills/ for Claude Code and .agents/skills/ for Codex and Cursor; global installs to ~/.claude/skills/, ~/.codex/skills/ and ~/.cursor/skills/. Run npx skills list: it prints each skill’s path and the agents that read it.
Teammates get different behaviour from the same skill. Someone has a stale copy or a personal skill with an overlapping description. Pin the team install to a tag, and run Dr. Skill to find overlapping skills and MCP servers.
A skill from outside the team behaves strangely. A skill can ship scripts the agent runs with your permissions. Read skill supply-chain security before installing third-party skills into a shared repository.