{"slug": "how-to-write-claude-code-skills-that-actually-trigger", "title": "How to Write Claude Code Skills That Actually Trigger", "summary": "SkillsBench, an arXiv benchmark published in February 2026, found that curated skills boosted average task pass rates from 33.9% to 50.5% across 87 tasks and 18 model-harness configurations, with healthcare jumping from 34.2% to 86.1% and manufacturing from 1.0% to 42.9%. The study's authors caution that skills were curated by evaluators themselves, making the direction reliable but magnitudes provisional. To make Claude Code skills trigger reliably, treat the description as a trigger router with verbs and user-typed phrases, keep it under 1,536 characters, and gate side effects with disable-model-invocation.", "body_md": "July 19, 2026\n\nTo write Claude Code skills that fire reliably, treat the description field as a trigger router, keep `SKILL.md`\n\nlean with progressive disclosure, gate side effects with `disable-model-invocation`\n\n, and test triggering in fresh sessions. Most skills fail for one reason: the description reads like documentation instead of matching what you actually type.\n\nClaude never reads a skill's body when deciding whether to use it. It reads one thing, the description in your frontmatter, and fuzzy-matches it against the task at hand. The [official skills docs](https://code.claude.com/docs/en/skills) put it plainly: \"What the skill does and when to use it. Claude uses this to decide when to apply the skill.\"\n\nTriggering happens two ways: you type the skill name as a slash command, or Claude matches your request against the description on its own. The second path is the one that breaks, and it breaks silently. No error, no log line, just a skill that never loads.\n\nSo your Claude skill description is a **trigger router**, not documentation. If a \"claude skill not triggering\" search brought you here, the description is the first thing to check and the most common fixable cause, with listing-budget eviction as the other frequent offender.\n\n```\n---\nname: pdf-helper\ndescription: A collection of utilities and best practices for working with PDF documents.\n---\n---\nname: pdf-helper\ndescription: Extract form fields, fill forms, redact text, or parse tables\n  from PDF files. Use when the user asks to fill, redact, or parse a PDF,\n  or mentions form fields, AcroForms, or PDF extraction.\n---\n```\n\nThe fixed version leads with verbs and the phrases a user would actually type. Order matters, because combined description text gets truncated at **1,536 characters** in the skill listing. Key use case first, background never.\n\nThere's also a **listing budget** most authors don't know exists. Skill descriptions share a pool that scales at 1% of the model's context window, and on overflow Claude Code drops descriptions \"starting with the skills you invoke least.\" Your least-used skill silently loses its trigger keywords first.\n\nWhere one user's 16,000-token skill listing footprint came from\n\nThe numbers get ugly fast. One user's `/context`\n\nreport in [issue #39686](https://github.com/anthropics/claude-code/issues/39686) showed 16,000 tokens of skill listings, with roughly 5,970 of those (3,950 from claude.ai skills, 2,020 from Cowork plugins) injected without the user ever asking. Skill listings stack on top of every other cost in [my token-usage teardown](/blog/guides/reduce-ai-coding-tool-token-usage).\n\nTip:Raise the pool with`skillListingBudgetFraction`\n\n, or mark a rarely-typed skill`\"name-only\"`\n\nin skillOverrides so it costs one name instead of a paragraph.\n\n[SkillsBench](https://arxiv.org/abs/2602.12670), an arXiv benchmark published in February 2026, ran 87 tasks across 18 model-harness configurations with and without curated skills. Average pass rate went from 33.9% to 50.5%, a **16.6-point lift**. It's the strongest evidence yet that skills reward craft, with one honest caveat: a single benchmark, 87 tasks, and skills curated by the evaluators themselves, so treat the direction as solid and the magnitudes as provisional.\n\n| Category | Without skills | With curated skills | |\n|---|---|---|---|\n| Healthcare | 34.2% | 86.1% | |\n| Manufacturing | 1% | 42.9% | |\n| Cybersecurity | 20.8% | 44% | |\n| Natural Science | 23.1% | 44.9% | |\n| Energy | 29.5% | 47.5% | |\n| Office & White Collar | 24.7% | 42.5% | |\n| Finance | 12.5% | 27.6% | |\n| Media & Content Production | 23.8% | 37.6% | |\n| Robotics | 20% | 27% | |\n| Mathematics | 41.3% | 47.3% | |\n| Software Engineering | 34.4% | 38.9% |\n\nLook at the spread, though. Healthcare jumped from 34.2% to 86.1% with skills and manufacturing went from 1.0% to 42.9%, while software engineering came dead last at 4.5 points of gain. Skills pay off most where the model lacks the procedure, and your team's weird deploy ritual is exactly that kind of procedure.\n\nOne finding should change how you write Claude Code skills: self-generated ones provide \"negligible or negative benefit on average.\" The authors conclude models \"cannot reliably author the procedural knowledge they benefit from consuming.\" So \"just ask Claude to write the skill for you\" is the lazy path the data says doesn't work.\n\nThis doesn't mean skip Claude during drafting. It means the human editing pass is where the value gets created.\n\nThe SKILL.md format is built around **progressive disclosure**, three levels of loading that keep idle context cost near zero. [Anthropic's engineering post](https://www.anthropic.com/engineering/equipping-agents-for-the-real-world-with-agent-skills) frames it as metadata first, full instructions on activation, then linked files Claude discovers \"only as needed.\"\n\nProgressive disclosure: what loads into context, and when\n\nLevel 1 is one name-plus-description line in the system prompt, paid in every session. Level 2, the SKILL.md body, loads only when the task matches the description. Level 3 files never enter context unless Claude decides it needs them.\n\nThat first level is the same listing budget from the previous section, which is why a tight description helps twice. It routes better and it costs less.\n\n```\npdf-form-filler/\n├── SKILL.md            # under 500 lines, imperative steps\n├── references/\n│   └── field-types.md  # deep docs, loaded only on demand\n├── scripts/\n│   └── fill_form.py    # executed via Bash, never read into context\n└── assets/\n    └── template.pdf    # copied into place, not read\n```\n\nThe docs' rule is blunt: keep SKILL.md **under 500 lines** and move deep reference material into `references/`\n\n. SkillsBench confirms it independently, finding that compact, detailed skills beat sprawling ones and that two to three modules is the sweet spot.\n\nDeterministic work belongs in `scripts/`\n\n. Anthropic's line: \"Sorting a list via token generation is far more expensive than simply running a sorting algorithm.\" A script also runs the same way every time, which is the real fix for a skill Claude follows inconsistently.\n\nThe strongest skills are project-scoped, not global. I spent weeks building skills in `~/.claude/skills/`\n\nbefore the obvious problem hit: my React project uses Formik and my Next.js project uses React Hook Form, and one global \"generate a form\" skill cannot serve both without awkward conditionals. A skill committed to `.claude/skills/`\n\ninside the repo encodes *your* component APIs, import paths, and validation library, and every teammate who pulls the branch gets identical output. No more \"what was that prompt you used for forms?\" in Slack.\n\nHere is a `/generate-form`\n\nskill that reads your existing FormBuilder component and scaffolds forms matching your conventions. Create the directory and write the body:\n\n```\nmkdir -p .claude/skills/generate-form\n---\ndescription: \"Generate a form component using our FormBuilder API\"\n---\n\n# Generate Form\n\nCreate a new form component following project conventions.\n\n## Instructions\n\n1. Read `src/components/ui/FormBuilder.tsx` to understand the current API.\n2. Accept a form name and field definitions from the user.\n3. Generate the form component in `src/components/forms/{FormName}Form.tsx`.\n4. Include Zod validation schema in `src/components/forms/{FormName}Form.schema.ts`.\n5. Add a Vitest test file in `src/components/forms/__tests__/{FormName}Form.test.tsx`.\n\n## Conventions\n\n- Use `useForm` from our FormBuilder, not raw React Hook Form.\n- Field names are camelCase. Labels are auto-generated from field names.\n- Every form gets a loading state and error boundary.\n- Export the form as a named export, not default.\n\n## Template\n\n// src/components/forms/{FormName}Form.tsx\nimport { useForm, FormBuilder, Field } from '@/components/ui/FormBuilder';\nimport { {formName}Schema } from './{FormName}Form.schema';\n\nexport function {FormName}Form({ onSubmit }) {\n  const form = useForm({\n    schema: {formName}Schema,\n    defaultValues: {/* generated from fields */},\n  });\n\n  return (\n    <FormBuilder form={form} onSubmit={onSubmit}>\n      {/* Fields generated here */}\n    </FormBuilder>\n  );\n}\n```\n\nThat body does two things a CLAUDE.md rule cannot: it reads your actual component source at run time, and it carries the exact template with your project's import paths. One developer [replaced their entire Plop.js generator](https://dev.to/mbarzeev/replacing-a-plop-react-component-generator-with-a-claude-code-skill-5do) with a single skill like this, and their takeaway is the reason project-scoped skills win: \"templates are more powerful than rules\" because a skill loads the template explicitly instead of hoping Claude remembers it from context.\n\nNow, instead of retyping a paragraph about your API, file naming, and validation library every session, you type one line:\n\n```\n/generate-form ContactForm: name (string, required), email (email, required), message (textarea)\n```\n\nClaude reads the skill, checks your real FormBuilder source, and emits the component, its Zod schema, and a matching test file, with correct imports in the right directory every time. The deterministic parts (file layout, boilerplate) live in the template where they run the same way each time; only the shape of *this* form is generated. That is the same `scripts/`\n\nover token-generation principle from the previous section, applied to the template.\n\nDo not build a library of twenty skills on day one. Find the single prompt you retype most (form generation, endpoint scaffolding, test files) and encode just that one, then commit the directory so your team gets it for free. Skills are not about being clever with the model; they are about being lazy in the right way: encode the pattern once, run it forever.\n\nMost tutorials stop at name and description. The fields that decide who invokes a skill, and in which context it runs, are the ones that separate a working skill from a liability. Three are worth knowing cold.\n\nSet `disable-model-invocation: true`\n\non anything that deploys, commits, releases, or messages other humans. The docs' own example is the right nightmare: you don't want Claude deciding to deploy because your code looks ready. The skill stays available to you as `/deploy`\n\n, but Claude can never fire it on its own.\n\n```\n---\nname: deploy\ndescription: Deploy the current branch to staging or production\ndisable-model-invocation: true  # only a human /deploy runs this\nargument-hint: [staging|production]\n---\n\nValidate that $ARGUMENTS is staging or production and stop if it is neither,\nrun scripts/preflight.sh against that target, then execute scripts/deploy.sh\nwith the validated target.\n```\n\n`context: fork`\n\nruns the skill body as a subagent prompt in a lean context that skips CLAUDE.md and your git status. Pair it with an `agent`\n\nfield to choose which subagent definition executes it. Your main session gets the result back without paying for the intermediate work, which is why it suits read-only, task-shaped skills like a codebase audit and not the deploy above (a fork can't stop to confirm anything with you).\n\nWarning:`context: fork`\n\nonly makes sense for skills with explicit instructions. A guideline-only skill (\"prefer functional style\") forked into a subagent has no task to execute and returns nothing useful.\n\n`argument-hint: [issue-number]`\n\nshows up in slash-command autocomplete, and `$ARGUMENTS`\n\nexpands to everything typed after the command, with `$0`\n\nand `$1`\n\navailable for positional access. Custom commands are skills now: a file at `.claude/commands/deploy.md`\n\ncreates `/deploy`\n\nthe same way a deploy skill does.\n\n```\n---\nname: fix-issue\ndescription: Fetch a GitHub issue, reproduce it, and implement a fix.\n  Use when the user references an issue number to fix.\nargument-hint: [issue-number]\ndisable-model-invocation: true\n---\n\nRun gh issue view $ARGUMENTS, reproduce the failure locally,\nimplement the fix, and reference the issue in the commit message.\n```\n\nThe `disable-model-invocation: true`\n\nline is load-bearing here: `$ARGUMENTS`\n\nis only populated on slash invocation, so an argument-shaped skill should be slash-only.\n\nThe same fields turn a skill into a repeatable micro-command: a checklist or workflow you trigger by name, with every authoring rule here applying unchanged. The project-scoped skill above is one worked shape; command-shaped skills are another.\n\nThe skills vs MCP question dominates the [Hacker News thread](https://news.ycombinator.com/item?id=45607117) on the launch, alongside variants like \"Isn't this the same as Cursor Rules?\" Fair confusion, since all four mechanisms put words into Claude's context. They differ in when, and at what cost.\n\n| Criterion | Skill | MCP server | CLAUDE.md | Subagent |\n|---|---|---|---|---|\n| Loads only when needed | ● | ◐ | ○ | ● |\n| Carries step-by-step procedure | ● | ○ | ◐ | ◐ |\n| Reaches external systems with auth | ◐ | ● | ○ | ◐ |\n| User-invokable as /command | ● | ○ | ○ | ○ |\n| Isolates work from main context | ◐ | ○ | ○ | ● |\n| Portable across projects and teams | ● | ● | ◐ | ◐ |\n\nNone of these are exclusive. A skill can call MCP tools mid-procedure, and a forked skill is literally a subagent running a skill body as its prompt. The triggering lens cuts both ways here: if a skill never fires no matter how you word the description, the procedure probably wants to be a tool or a subagent instead.\n\nMy split: one MCP server per external system, then thin skills that orchestrate those tools into a workflow. If a CLAUDE.md block only matters during one kind of task, it's a skill wearing the wrong file. I keep the server list itself short for the same budget reasons, see [which MCP servers are worth installing](/blog/guides/best-mcp-servers-claude-code).\n\nA skill you've only exercised in the session where you wrote it is untested. That session already has the whole skill in context, so everything triggers and everything gets followed. Fresh sessions are the only honest test environment.\n\nAnthropic's debugging checklist reduces to one line: check the description includes keywords users would naturally say. When a phrase misses, triage in order: first confirm the description actually made it into the listing (budget eviction is silent), then run the phrase two or three times in fresh sessions, because triggering is probabilistic. A 2-out-of-3 hit on a must-trigger phrase is a fail, and only after both checks should you add keywords.\n\nFor drafting, use the loop the engineering post suggests: ask Claude to capture its successful approaches and common mistakes into a skill. Then edit like an owner. SkillsBench showed the unedited draft is worth roughly nothing.\n\nCut the filler and compress to two or three modules. Then rewrite the description trigger-first, because you write Claude Code skills the way you write tests: adversarially. That editing pass is the difference between curated skills (+16.6pp average) and self-generated ones (roughly nothing).\n\nBefore inventing shapes, steal them. [obra/superpowers](https://github.com/obra/superpowers) is a 257k-star (as of July 2026) corpus of composed skills with an explicit methodology, and it's the best worked example of everything above.\n\nClaude Code skills: what developers actually ask\n\nA project (or CLAUDE.md) scopes context to one codebase. A skill packages a repeatable procedure you want in every codebase: if you would copy the same instructions into a second project, that is the signal to extract a skill. Personal skills live in ~/.claude/skills and follow you across every repo.\n\nNo. Rules files sit in context for the whole session whether they are relevant or not. A skill costs one description line until its trigger matches, then loads its full body. That lets you maintain far more procedural knowledge without paying idle context cost for all of it.\n\nCLAUDE.md is for always-true facts about the project (build commands, conventions, constraints that apply to every task). A skill is for on-demand procedure that only some tasks need. If a block of CLAUDE.md only matters during one kind of task, it belongs in a skill.\n\nKeep the body short and imperative, structure steps as a checklist, and test in fresh sessions rather than the one where you wrote it. For workflows that must run exactly, set disable-model-invocation: true and invoke the skill yourself, or move the deterministic parts into scripts/ so Claude executes code instead of interpreting prose.\n\nYes, and the pairing changes what the skill can do: context: fork runs the skill body as a subagent prompt, which means it gets a clean context (no CLAUDE.md, no conversation history) and returns only its final result. Pick the agent field to control which tools it gets. Budget bonus: the fork cannot see your conversation, so any input it needs must arrive through arguments.\n\nNext action: open your worst-triggering skill, rewrite its description trigger-first, and run a five-phrase matrix in fresh sessions. Twenty minutes, and you'll know exactly why it wasn't firing.", "url": "https://wpnews.pro/news/how-to-write-claude-code-skills-that-actually-trigger", "canonical_source": "https://rizz.dev/blog/guides/create-claude-code-skills", "published_at": "2026-07-19 00:00:00+00:00", "updated_at": "2026-07-23 10:09:04.202764+00:00", "lang": "en", "topics": ["developer-tools", "large-language-models", "ai-tools"], "entities": ["Claude Code", "SkillsBench", "arXiv", "Anthropic"], "alternates": {"html": "https://wpnews.pro/news/how-to-write-claude-code-skills-that-actually-trigger", "markdown": "https://wpnews.pro/news/how-to-write-claude-code-skills-that-actually-trigger.md", "text": "https://wpnews.pro/news/how-to-write-claude-code-skills-that-actually-trigger.txt", "jsonld": "https://wpnews.pro/news/how-to-write-claude-code-skills-that-actually-trigger.jsonld"}}