{"slug": "writing-effective-claude-code-skills", "title": "Writing effective Claude Code skills", "summary": "A developer detailed best practices for writing production-grade Claude Code skills, describing a three-level loading mechanism that governs how skill metadata, instructions, and reference files are pulled into an agent's context. The writeup emphasizes that the frontmatter description is the only text Claude sees before deciding to activate a skill, and recommends keeping the SKILL.md body under 5,000 tokens while offloading larger content to reference files. The author notes the SKILL.md format is an open standard created by Anthropic and adopted by other agent products such as Cursor.", "body_md": "Skills are the most underrated feature in [Claude Code](https://docs.anthropic.com/en/docs/claude-code). I use dozens of them covering everything from Git commits to blog article creation. Most developers have none, or one or two copied from a tutorial.\n\nThis article shows how to write skills that work in production: the structure, the loading mechanism, the patterns I've identified after months of iteration, and the common mistakes.\n\nA skill is a folder containing a `SKILL.md` file. The file combines a YAML frontmatter (metadata) and a Markdown body (instructions). When the user types `/skill-name` or phrases a request that matches the description, Claude loads the instructions and follows them. The [official documentation](https://docs.anthropic.com/en/docs/claude-code/skills) covers installation and basic syntax.\n\n```\ncommit-push/\n  SKILL.md          # required: metadata + instructions\n  references/       # optional: detailed docs\n  scripts/          # optional: executable code\n  assets/           # optional: templates, static files\n```\n\nThe SKILL.md format is an open standard created by Anthropic and adopted by other agent products like [Cursor](https://docs.cursor.com/context/skills). A skill written for Claude Code works as-is in these tools.\n\nThe loading mechanism is the most important aspect to understand. Without it, you write skills that are too large and waste context, or too vague and never trigger.\n\nLoading happens in three levels:\n\n**Level 1 - Discovery (~100 tokens per skill).** Only the `name` and `description` from the frontmatter are injected into the system prompt at the start of each session. Claude knows the skill exists and when it applies. Even with dozens of active skills, that's only a few thousand tokens - negligible in a 200K context.\n\n**Level 2 - Activation (<5,000 tokens).** When the user's request matches a skill's description, Claude reads the full `SKILL.md` body. This is where the detailed instructions, step-by-step workflows, and checklists live.\n\n**Level 3 - Execution (on demand, unlimited size).** The agent reads files from the `references/` folder or runs scripts from `scripts/` only when the Level 2 instructions tell it to. A blog skill might have 20 reference files, but Claude only loads 2 or 3 per execution.\n\nThe practical consequence: the `SKILL.md` body should stay under 5,000 tokens. Anything beyond that belongs in `references/`. If the skill is too large, Claude loads thousands of context tokens every activation, even when it only needs a fraction.\n\nHere's the minimal structure:\n\n```\n---\nname: my-skill\ndescription: \"|\"\n  What this skill does AND when to use it.\n  Include trigger phrases in the languages your users speak.\n---\n\n# My Skill\n\n## Workflow\n1. First step\n2. Second step\n3. Third step\n\n## Output format\nWhat the user should receive in return.\n```\n\nThe frontmatter is the most critical component. The `description` is the **only text** Claude sees before deciding whether to activate the skill. If it's vague, the skill won't trigger.\n\nTechnical constraints:\n\n`name` in lowercase with hyphens only, 1-64 characters\nHere's the description from one of my production skills:\n\n```\nname: commit-push\ndescription: |\n  Commit all changes with an auto-generated conventional commit message\n  and push to remote, all in one step.\n  Use when: \"commit & push\", \"commit and push\", \"commit push\",\n  \"/commit-push\", \"commit et pousse\", \"pousse ca\", \"fais un commit\n  et push\", \"commit tout\".\n```\n\nThree elements to note:\n\nIf I had only written \"Commit and push changes\", the skill would trigger on \"commit and push\" but not on \"pousse ca\" or \"commit tout\".\n\nThe SKILL.md body contains the workflow Claude should follow. Here's a simplified excerpt from my `/lint-check` skill:\n\n```\n# Lint Check\n\nRun the full Rust lint pipeline and auto-fix errors.\n\n## Workflow\n\n1. Run `cargo fmt -- --check` to detect formatting issues\n2. If formatting issues found, run `cargo fmt` to fix them\n3. Run `cargo clippy -- -D warnings` to detect lint issues\n4. If clippy issues found, fix them one by one\n5. Run `cargo check` to verify compilation\n6. If errors remain after 3 fix attempts, stop and report\n\n## Error handling\n\n| Scenario | Action |\n|----------|--------|\n| cargo fmt fails | Report the error, do not continue |\n| clippy warns but compiles | Fix warnings, re-run |\n| cargo check fails | Show the error, suggest a fix |\n```\n\nThe skill is 30 lines, not 300. It says what to do, in what order, and how to handle errors. Claude doesn't need an explanation of what `cargo clippy` is - it already knows.\n\nThe skill is a thin wrapper around a CLI or deterministic script. The logic lives in the code; the skill just orchestrates it.\n\nExamples from my setup:\n\n`/lint-check` orchestrates `cargo fmt`, `cargo clippy`, `cargo check`\n`/commit-push` orchestrates `git status`, `git diff`, `git log`, `git add`, `git commit`, `git push`\n`/seo-scan` orchestrates an SEO crawler and updates a tracking file\n[MCP RTK](https://dev.to/blog/en/mcp-rtk-reduce-token-usage-mcp-servers) is a good example of this pattern: the skill orchestrates a token filtering proxy via CLI commands, with no logic in prose.\n\nThe skill encodes a methodology the agent must follow. It's pure prompt engineering - no script to run, just a thinking process.\n\nExamples:\n\n`/systematic-debugging` enforces a 5-step debugging methodology (reproduce, isolate, hypothesize, verify, fix)`/security-audit` defines an OWASP top 10 checklist with patterns to search by category\nPattern B is harder to write well. The temptation is to over-explain. Claude knows how to debug - the skill adds **structure** to the process, not knowledge.\n\nAfter months of iterating, here are the patterns that work.\n\nA code block the agent can execute beats a paragraph describing what to do.\n\nBad:\n\n```\nCheck if there are uncommitted modified files in the current\ngit repository by using the git status command.\n```\n\nGood:\n\n```\n1. Run `git status` to check for uncommitted changes\n```\n\nClaude knows what `git status` does. The short version is clearer and more reliable.\n\nAlways verify the current state before modifying anything. Without this, Claude acts on assumptions and breaks things.\n\n```\n## Workflow\n1. Run `git status` to verify clean working tree\n2. Run `git log --oneline -5` to confirm current branch\n3. Only then: create the feature branch\n```\n\nAfter each action, verify the result is correct before moving to the next. My `/dev-pipeline` skill does this at every step:\n\n```\n4. Run `cargo clippy -- -D warnings`\n5. If clippy reports errors:\n   a. Fix the errors\n   b. Re-run clippy\n   c. If errors persist after 3 attempts, stop and report\n6. Only if clippy passes: proceed to tests\n```\n\nWithout validation loops, Claude chains steps even when one fails. It ends up \"completing\" a pipeline where every step has failed.\n\nDon't bundle entire workflows into a single skill. Compose simple skills together.\n\nMy `/dev-pipeline` skill doesn't reimplement linting - it calls `/lint-check`. It doesn't reimplement commits - it calls `/commit-push`. Each skill does one thing, and workflows compose these primitives.\n\n```\n# Dev Pipeline\n\n1. Plan the implementation (use EnterPlanMode)\n2. Implement the changes\n3. Run `/lint-check` to verify code quality\n4. Run tests\n5. Run `/commit-push` to commit and push\n```\n\nThe agent must know exactly what it produces. Without an output specification, Claude improvises a different format every execution.\n\n```\n## Output format\n\nDeliver a summary with:\n- Files modified: list of paths\n- Tests: pass/fail count\n- Lint: pass/fail with details\n- Commit: the conventional commit message used\n# Bad\nJSON (JavaScript Object Notation) is a structured data format\nused for data exchange...\n\n# Good\nGenerate a JSON response matching the schema in references/schema.md.\n```\n\nClaude knows what JSON is. Every token wasted on unnecessary pedagogy is a context token lost.\n\n```\n# Bad - will almost never trigger\nname: helper\ndescription: Helps with dev stuff\n\n# Good - clear and specific triggers\nname: lint-check\ndescription: |\n  Run the full Rust lint pipeline (cargo fmt, clippy, check)\n  and auto-fix errors. Trigger on: \"lint check\", \"lance le lint\",\n  \"cargo fmt && cargo clippy\", \"check my Rust code\".\n```\n\nA vague description means Claude doesn't know when to activate the skill. It has dozens of descriptions to compare against the user's request - precision is essential.\n\nOne skill = one capability. If the description contains \"and\" between two independent actions, it's probably two skills.\n\nMy first `/dev-pipeline` was 500 lines with everything inline: linting, tests, review, commit, push, MR creation. Today it's 40 lines and composes five specialized skills.\n\nDocument what can go wrong and how to react. Without this, Claude stops or invents a solution when a command fails.\n\n```\n## Error handling\n\n| Scenario | Action |\n|----------|--------|\n| No git remote configured | Stop, ask user to configure |\n| Pre-commit hook fails | Fix the issue, retry once |\n| Push rejected (not fast-forward) | Run `git pull --rebase`, retry |\n| Merge conflict after rebase | Stop, show conflicts to user |\n# Bad\nRead /Users/thomas/.claude/scripts/validate.py\n\n# Good\nRead scripts/validate.py from the skill directory\n```\n\nAbsolute paths break when the skill is shared or used on another machine. Paths relative to the skill directory or environment variables are portable.\n\nThe most powerful skills are those adapted to a specific project. In [my setup](https://dev.to/blog/en/claude-code-setup-2026), the Netir project has six skills that encode the project's conventions:\n\n```\n# netir-cpm/SKILL.md (excerpt)\nname: netir-cpm\ndescription: |\n  Commit, push and create a GitLab Merge Request for the Netir\n  project. Netir conventions applied: assignee ThomasTartrau,\n  reviewer netir-bot, label \"MR::en attente de review\".\n  Use when: \"/cpm\", \"create the MR\", \"commit push mr\".\n```\n\nThe difference from the generic `/cpm`: Netir conventions (labels, reviewer, assignee) are hardcoded. I don't need to re-specify them for every MR.\n\nAnother example: `/netir-qa-swarm` launches four review agents in parallel, each with a different focus:\n\n```\n## Agents\n\n| Agent | Focus |\n|-------|-------|\n| Architecture | Layers, separation of concerns |\n| Security | OWASP, injections, auth, rate limiting |\n| Rust quality | Idioms, clippy, performance, unwrap |\n| Business patterns | Domain coherence, naming, edge cases |\n```\n\nEach agent has instructions specific to the Netir codebase (the [Axum](https://github.com/tokio-rs/axum)/SQLx stack, naming conventions, error patterns). A generic review skill doesn't know these conventions.\n\nThe most important test: invoke the skill with varied phrasings and verify it triggers.\n\nUsers don't say \"/invoke-my-skill\". They say:\n\nIf these natural phrasings don't trigger the skill, the description needs work. I add failing phrasings to the frontmatter triggers.\n\nThe second test: verify the instructions produce the expected result on a real case. No imaginary dry-run - run the skill on an actual project and check the output.\n\nBefore deploying a new skill to my configuration repo:\n\n`references/`, not the body\n**The description is the router.** Invest as much time on the description as on the body. A skill with perfect instructions but a vague description will never trigger.\n\n**Code beats prose.** A deterministic script is always more reliable than ambiguous instructions. If a task has a single correct answer, put the logic in a script, not in Markdown.\n\n**The cheapest context is context you don't load.** Progressive disclosure exists for a reason. A 200-line skill that could be 40 with references wastes context on every activation.\n\n**Test with real phrasings.** Users don't type clean commands. They write \"commit this\", \"push it\", \"lint\" - triggers must cover these variants.\n\nSkills transform Claude Code from a generic assistant into a tool adapted to your specific workflow. The [projects page](https://dev.to/projects) lists the other tools I've built around this ecosystem.", "url": "https://wpnews.pro/news/writing-effective-claude-code-skills", "canonical_source": "https://dev.to/thomastartrau/writing-effective-claude-code-skills-145l", "published_at": "2026-09-11 16:54:10+00:00", "updated_at": "2026-09-11 17:14:01.580382+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "developer-tools", "large-language-models", "ai-products"], "entities": ["Claude Code", "Anthropic", "Cursor"], "alternates": {"html": "https://wpnews.pro/news/writing-effective-claude-code-skills", "markdown": "https://wpnews.pro/news/writing-effective-claude-code-skills.md", "text": "https://wpnews.pro/news/writing-effective-claude-code-skills.txt", "jsonld": "https://wpnews.pro/news/writing-effective-claude-code-skills.jsonld"}}