Skip to content

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-migration skill: SKILL.md, a review checklist in references/ and a helper script
  • The scaffolding command for each route: npx skills init, claude plugin init, and the skill-creator skill 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 eval against 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 situationBuild
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 itA plugin that bundles the skill
A live connection to an internal API or databaseAn 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.

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 line

The frontmatter rules come from the specification; the reference validator in the next section rejects a file that breaks them:

FieldRequiredRule
nameYes1–64 characters, lowercase letters, digits and single hyphens; no leading or trailing hyphen; must match the folder name
descriptionYes1–1,024 characters; says what the skill does and when to use it
license, compatibility, metadataNocompatibility is at most 500 characters and states environment needs (“Requires git and jq”)
allowed-toolsNoExperimental, 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-migration
description: 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
```sql
ALTER 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:

skills/db-migration/references/checklist.md
# 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 reply

The 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 number
set -euo pipefail
last=$(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.

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):

Terminal window
cd acme-agent-skills/skills
npx skills init db-migration # creates db-migration/SKILL.md with a TODO description

For a personal skill, scaffold a skills-dir plugin (run):

Terminal window
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.

Terminal window
claude plugin install skill-creator@claude-plugins-official

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.

  1. Validate the format with two validators. claude plugin validate --strict checks the manifest and every skill’s frontmatter and exits 1 on any warning. The reference validator from the specification’s skills-ref library checks the naming rules that Claude Code tolerates (run):

    ./acme-agent-skills/skills/db-migration
    claude plugin validate --strict ./acme-agent-skills
    uvx --from skills-ref==0.1.1 agentskills validate ./acme-agent-skills/skills/db-migration

    On 2.1.283, validate --strict failed a skill with no description and a file with no frontmatter, but passed name: Bad_Name. The reference validator rejected it: “Skill name ‘Bad_Name’ must be lowercase”. The skills-ref README calls the library “intended for demonstration purposes only”, so use it as a lint, not a gate you depend on.

  2. Give Claude Code a manifest to evaluate. claude plugin eval runs 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 skills at ./skills/, not ./. A fresh claude plugin init scaffold declares "skills": ["./"], and claude plugin eval init then refuses to write because evals/ would overlap a component path.

  3. Write the eval cases. claude plugin eval init --bare new-column creates evals/new-column/prompt.md (the task, max_turns, allowed_tools) and evals/new-column/graders/criteria.md (an LLM-graded definition of success). Fill in a case that must trigger the skill:

    ---
    max_turns: 10
    allowed_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: llm
    weight: 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_tools deliberately 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.

  4. 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-publish

    The command exits 1 when any case scores below the threshold. The default --ablation with-without adds the no-skill arm. A skill that scores no better than its baseline is dead weight. Add a with-only grader tool_used: Skill so the report shows whether the skill fired, separately from the score; claude plugin eval --help in 2.1.286 lists such graders as a plugin-fired indicator under with-without. Eval runs the plugin on your machine as you, so pass --trust-plugin only for skills you wrote.

  5. Read the context cost. Load the checkout with --plugin-dir and read what the skill adds to every session and what it costs when it fires. Without --plugin-dir, plugin details looks only at installed plugins and reports Plugin "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 session

    The figure depends on the description: ~98 tokens is the db-migration description above. A description that grows past a few sentences shows up here first.

  6. 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 -p runs 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 -y
    codex 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.

A pushed public repository is installable at once; nothing needs approving:

Terminal window
npx skills add acme/agent-skills --skill db-migration -a claude-code -a codex -a cursor

Pin 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.

Terminal window
git tag v1.2.0 && git push origin v1.2.0
npx skills add acme/agent-skills#v1.2.0 --skill db-migration -a claude-code

Private 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:

  1. Change the convention and the skill in one pull request. When the team switches migrations to a new format, the same PR edits SKILL.md and adds an eval case for the new rule.

  2. CI runs the format gate. claude plugin validate --strict needs no model and no API key, so it runs on every PR:

    .github/workflows/skills.yml
    name: skills
    on: pull_request
    permissions:
    contents: read
    jobs:
    validate:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v7 # pin to a full commit SHA in your repository
    with:
    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
  3. The owner runs the eval before merge and pastes the with-skill and without-skill scores into the PR.

  4. Tag the release after merge, and move teammates to it with npx skills update or 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.