How to Write Claude Code Skills That Actually Trigger 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. July 19, 2026 To write Claude Code skills that fire reliably, treat the description field as a trigger router, keep SKILL.md lean with progressive disclosure, gate side effects with disable-model-invocation , and test triggering in fresh sessions. Most skills fail for one reason: the description reads like documentation instead of matching what you actually type. Claude 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." Triggering 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. So 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. --- name: pdf-helper description: A collection of utilities and best practices for working with PDF documents. --- --- name: pdf-helper description: Extract form fields, fill forms, redact text, or parse tables from PDF files. Use when the user asks to fill, redact, or parse a PDF, or mentions form fields, AcroForms, or PDF extraction. --- The 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. There'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. Where one user's 16,000-token skill listing footprint came from The numbers get ugly fast. One user's /context report 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 . Tip:Raise the pool with skillListingBudgetFraction , or mark a rarely-typed skill "name-only" in skillOverrides so it costs one name instead of a paragraph. 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. | Category | Without skills | With curated skills | | |---|---|---|---| | Healthcare | 34.2% | 86.1% | | | Manufacturing | 1% | 42.9% | | | Cybersecurity | 20.8% | 44% | | | Natural Science | 23.1% | 44.9% | | | Energy | 29.5% | 47.5% | | | Office & White Collar | 24.7% | 42.5% | | | Finance | 12.5% | 27.6% | | | Media & Content Production | 23.8% | 37.6% | | | Robotics | 20% | 27% | | | Mathematics | 41.3% | 47.3% | | | Software Engineering | 34.4% | 38.9% | Look 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. One 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. This doesn't mean skip Claude during drafting. It means the human editing pass is where the value gets created. The 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." Progressive disclosure: what loads into context, and when Level 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. That 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. pdf-form-filler/ ├── SKILL.md under 500 lines, imperative steps ├── references/ │ └── field-types.md deep docs, loaded only on demand ├── scripts/ │ └── fill form.py executed via Bash, never read into context └── assets/ └── template.pdf copied into place, not read The docs' rule is blunt: keep SKILL.md under 500 lines and move deep reference material into references/ . SkillsBench confirms it independently, finding that compact, detailed skills beat sprawling ones and that two to three modules is the sweet spot. Deterministic work belongs in scripts/ . 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. The strongest skills are project-scoped, not global. I spent weeks building skills in ~/.claude/skills/ before 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/ inside 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. Here is a /generate-form skill that reads your existing FormBuilder component and scaffolds forms matching your conventions. Create the directory and write the body: mkdir -p .claude/skills/generate-form --- description: "Generate a form component using our FormBuilder API" --- Generate Form Create a new form component following project conventions. Instructions 1. Read src/components/ui/FormBuilder.tsx to understand the current API. 2. Accept a form name and field definitions from the user. 3. Generate the form component in src/components/forms/{FormName}Form.tsx . 4. Include Zod validation schema in src/components/forms/{FormName}Form.schema.ts . 5. Add a Vitest test file in src/components/forms/ tests /{FormName}Form.test.tsx . Conventions - Use useForm from our FormBuilder, not raw React Hook Form. - Field names are camelCase. Labels are auto-generated from field names. - Every form gets a loading state and error boundary. - Export the form as a named export, not default. Template // src/components/forms/{FormName}Form.tsx import { useForm, FormBuilder, Field } from '@/components/ui/FormBuilder'; import { {formName}Schema } from './{FormName}Form.schema'; export function {FormName}Form { onSubmit } { const form = useForm { schema: {formName}Schema, defaultValues: {/ generated from fields /}, } ; return