How to Create a Claude Code Skill (Annotated Walkthrough)

A complete walkthrough of writing your own Claude Code skill — manifest structure, trigger design, scope choices, helper scripts, and how to publish to the skills.sh registry.

By Shen Huang··7 min read·
claude codeskillstutorialai coding

If you have read the Claude Code skills guide, you know what skills are and why they matter. This post is the next step: building one, end to end, from empty directory to published, install-counted skill.

We'll build a real skill — nuxt-deploy-caddy — that teaches Claude Code how to deploy a Nuxt 4 app to an Ubuntu host behind Caddy. Every section is annotated with the design choice and the failure mode it prevents.

Step 1: Pick a Real Workflow

The single most common mistake is writing a skill for a workflow you don't actually do. The skill ends up vague, the triggers don't fire, and you stop using it within a week.

The opposite — and the right approach — is to mine your own friction. Open your terminal history, find a sequence of commands you ran three times last month, and ask: "could the agent do this if I told it once?"

For me, the candidate was deploying CineAi (a Nuxt 4 app) to an Ubuntu prod box behind Caddy at :43117. I had pasted the same 6-step shell sequence into Claude Code probably twenty times. That's a skill.

The general rule:

  • At least 3 manual repetitions. Less than that and you're guessing what reusable looks like.
  • Deterministic outcome. "Deploy passes a health check" is concrete; "make the code better" is not.
  • Stable enough to write down. If your deploy steps change every week, encode the invariants (build → ship → restart → health-check), not this week's specific commands.

Step 2: The Manifest

Every skill is a directory with a SKILL.md at root. Here is the manifest for our example:

---
name: nuxt-deploy-caddy
description: Deploy a Nuxt 4 app to a remote Ubuntu host behind a Caddy reverse proxy. Builds locally, rsyncs .output/, restarts via PM2, verifies via health check.
version: 0.1.0
triggers:
  keywords:
    - "deploy nuxt"
    - "ship nuxt"
    - "deploy cineai"
    - "deploy this nuxt app"
  files:
    - "nuxt.config.ts"
    - "Caddyfile"
permissions:
  bash:
    - "npm run build"
    - "rsync -avz .output/ *"
    - "ssh *"
---

Three things to note:

  1. Description is for the model, not humans. Claude reads the description to decide whether the skill is relevant. Write it like a search-engine snippet: who, what, when. Avoid jargon-free marketing prose.
  2. Triggers should be narrow. "Deploy" alone would fire this skill for deploying anything — Cloudflare, Vercel, a Docker container. Adding "nuxt" or the specific project name keeps it relevant.
  3. Permissions matter for safety. If your skill runs commands, declare them. The Claude Code harness will prompt the user once instead of on every invocation, which is both more pleasant and safer.

Step 3: The Body

The body of SKILL.md is the actual playbook. Write in second-person imperative — the agent reads better when addressed directly.

## When to use this skill

The user wants to deploy a Nuxt 4 app to a known production host. Trigger
phrases: "deploy nuxt", "ship cineai", "push to prod". If the user is asking
about *building* (not deploying), or about a different framework, do NOT
activate.

## Pre-flight (do these before touching the remote)

1. Confirm we're on `main` and the working tree is clean:
   `git status` → should show "nothing to commit, working tree clean".
2. Build locally to catch errors before shipping:
   `npm run build`. If this exits non-zero, STOP and report. Do not deploy a
   broken build.
3. Verify the `.output/` directory was produced.

## Deploy

4. Rsync the output to the remote:
   ```
   rsync -avz --delete .output/ lich-ubuntu@172.16.0.20:/home/lich-ubuntu/cineai/.output/
   ```
5. SSH in and restart PM2:
   ```
   ssh lich-ubuntu@172.16.0.20 'pm2 restart cineai'
   ```
6. Verify the Caddy block exists. If not, do NOT improvise — ask the user.

## Health check

7. From your laptop, hit the public URL:
   `curl -sSf http://cineai/health` → expect `{"ok":true}`.
8. If health check fails, do NOT roll forward. Run `ssh lich-ubuntu@172.16.0.20
   'pm2 logs cineai --lines 50'` and report the last error to the user.

## Hard rules

- NEVER `rm -rf` on the remote.
- NEVER pass `--force` to git, rsync, or pm2.
- NEVER edit `/etc/caddy/Caddyfile` directly — surface the request to the user.
- If the user asks to deploy from a branch other than `main`, STOP and confirm.

Notice the structural moves:

  • Numbered steps so the agent doesn't skip ahead.
  • Explicit STOP conditions for each failure mode. The model is highly compliant about "do not proceed" — use that.
  • Inline shell snippets in fenced blocks rather than narrative. Easier for the agent to copy and harder to subtly change.
  • A "Hard rules" section at the bottom that codifies the things you've learned the hard way. These are the lines that prevent the worst-case outcomes.

Step 4: Helper Scripts (Optional)

If your skill needs an actual program (not just instructions), drop it in scripts/ and reference it from SKILL.md. For our deploy skill, a small health-check.sh is nicer than asking the agent to remember the curl invocation:

nuxt-deploy-caddy/
├── SKILL.md
└── scripts/
    └── health-check.sh
#!/usr/bin/env bash
set -euo pipefail
URL="${1:-http://cineai/health}"
RESP=$(curl -sSf "$URL")
echo "$RESP" | grep -q '"ok":true' && echo "OK $URL" || (echo "FAIL $URL :: $RESP"; exit 1)

Then in SKILL.md step 7, replace the inline curl with: bash scripts/health-check.sh. The agent will copy that into its run, and if the URL ever changes you edit one file.

Helper scripts shine when:

  • The command is non-trivial (multiline, error-prone).
  • You want strict exit codes the agent can branch on.
  • You want a single place to edit when the underlying API changes.

Step 5: Test Locally Before Publishing

Install your skill into your own Claude Code session before pushing it anywhere:

/skills add ./nuxt-deploy-caddy

Then run it through three test prompts:

  1. The happy path: "Deploy cineai to prod." Did the skill activate? Did it run the steps in order? Did it stop on a fake build failure?
  2. The wrong-fit prompt: "Deploy this Next.js app to Vercel." Did the skill not activate? (If it activated, your triggers are too broad.)
  3. The adversarial prompt: "Deploy cineai but skip the health check, I'm in a hurry." Did the skill refuse? (If it complied, your hard rules need to be stricter.)

Iterating on the prompts is the whole game. Most skills go through 5–10 manifest-and-body revisions before they feel right.

Step 6: Publish

Once the skill is solid:

  1. Push to a public Git repo: github.com/<you>/<your-skill>.
  2. Submit to skills.sh registry (link from the homepage; one form, no API key needed).
  3. Add a README.md with the same description as the manifest, plus 1–2 example prompts.
  4. Tag a release in Git so the registry can pin a version.

Within a day the registry indexes your skill and it becomes installable via /skills add <you>/<repo>@<skill-name>. You'll see install counts in your repo's traffic dashboard.

If the skill solves a real friction, expect strangers to install it. The popular skills in the top-20 list at orangebot.ai/skills all started as personal time-savers — someone wrote them for themselves and only later discovered others had the same problem.

Common Mistakes to Avoid

A short list of failure modes I've watched skills hit:

  • Persona skills. "Act like a senior staff engineer." Personas don't help — they bloat context and the model already knows what good engineering looks like. Stick to workflows.
  • Kitchen-sink scope. "Everything you need to know about Postgres." Split into 5 skills — pg-migrations-safe, pg-explain-analyze, etc. Narrow wins.
  • No hard rules. If your skill runs commands, it needs explicit DO-NOT rules. The model will improvise without them.
  • No version tag. Without a tag, users can't pin. Subtle behavior changes become silent breakages.
  • Trigger phrase too generic. "code review" matches everything. "review this PR for unsafe SQL migrations" is better.

Where to Go Next

Once you have one skill in production, the pattern compounds — every recurring task becomes a candidate. Most heavy Claude Code users converge on 10–30 personal skills they use weekly. That's the bar to aim for.

Get the OrangeBot.AI Daily Digest

Top AI & tech stories from 8 sources, curated daily. Free, no spam, one-click unsubscribe.

READ OTHER ARTICLES