Skill Design Principles
Most skills underperform because they describe what the skill does instead of teaching Claude what to do differently. A skill is a behavioral directive, not a feature description. The difference sounds subtle — it determines whether a skill actually changes Claude’s output or just adds noise to the context.
Attribution: These 9 principles come from Thariq, an Anthropic engineer specializing in skill architecture, shared in March 2026.
1. Don’t State the Obvious
A skill should challenge Claude’s default behavior, not restate it. If Claude would already do something without the skill, writing “do X” in the skill adds tokens but changes nothing.
The question to ask before every line: “Would Claude do this anyway?” If yes, cut it.
Bad:
When reviewing code, look for bugs and suggest improvements.Good:
When reviewing code, assume the author is a senior engineer.Skip obvious style notes. Focus on architectural implications,failure modes under load, and edge cases in error handling.The bad version restates what Claude always does. The good version overrides the default behavior (explain everything, include style notes) with something more useful for the specific context.
2. Build a Gotchas Section
The Gotchas section is the highest-signal content in any skill. It is the list of specific failure patterns Claude keeps hitting when operating in this domain — and what to do instead.
## Gotchas
- **Do not suggest moving to a monorepo** unless the user explicitly mentions build tooling problems. Teams asking about module structure are not asking about monorepos.
- **Do not recommend TypeScript strict mode** mid-project. The question is never worth the migration cost unless the user raised type safety first.
- **When the user says "refactor for readability," do not reduce abstraction.** Readability requests usually mean "fewer magic numbers and better names," not "inline everything."A skill without a Gotchas section is untested. The Gotchas section is how you record what you learn from real usage — update it every time you catch Claude making the same mistake twice.
Format
## Gotchas
- **[Short label]**: [What Claude does wrong] / [What to do instead]- **[Short label]**: [What Claude does wrong] / [What to do instead]Concrete, specific, action-oriented. Not “be careful about X” — “when you see X, do Y instead.”
3. Use the File System for Progressive Disclosure
A skill is a folder, not a file. SKILL.md is the entry point — it should stay concise (under 200 lines). Supporting content lives in subdirectories that Claude discovers contextually.
.claude/skills/dashboard-design/├── SKILL.md ← entry point, < 200 lines├── references/│ ├── color-system.md│ ├── chart-type-guide.md│ └── accessibility-checklist.md├── examples/│ ├── good-layout.png│ └── anti-patterns.png├── templates/│ └── dashboard-starter.tsx└── scripts/ └── validate-contrast.pyClaude does not load the entire folder at once. It loads SKILL.md and discovers subdirectory content when the task calls for it. This keeps the active context tight while making deep reference material available on demand.
Heavy content — large reference tables, datasets, long examples — belongs in subdirectories, not inline in SKILL.md. Inline content that will never be needed for a given invocation wastes context on every use.
4. The Description Field Is a Trigger, Not a Summary
The description field in a skill drives auto-invocation. Claude reads it to decide whether to activate the skill. Write it as a conditional — the condition under which the skill should fire — not as a summary of what the skill contains.
Bad:
{ "description": "This skill helps with dashboard design and data visualization."}Good:
{ "description": "Use when designing or critiquing a dashboard layout, data visualization component, or admin interface. Also activate when the user asks why a chart is hard to read."}The bad version describes the skill. The good version tells Claude when to use it. Write it the way you would write the condition in an if-statement.
5. Avoid Railroading Claude
Step-by-step procedures in skills produce brittle, mechanical behavior. Claude is better at reaching a goal within constraints than following a fixed procedure. Give it the destination and the boundaries — not a numbered list of actions.
Bad:
1. Analyze the current codebase structure2. Identify files that need changes3. List the changes required4. Implement changes in order5. Write tests for each change6. Update documentationGood:
Your goal is a clean, working implementation with test coverage.Constraints: do not change the public API surface, keep files under200 lines, do not introduce new dependencies without flagging them.Use your judgment on sequencing — the order matters less than the outcome.The railroaded version forces Claude into a sequence that may not match what the task actually needs. The constraints-based version lets Claude find the most efficient path while respecting the limits that actually matter.
6. Think Through the Setup
If a skill requires user-specific configuration (API endpoints, personal preferences, project paths), store that configuration in a config.json in the skill directory. If the config does not exist when the skill is invoked, Claude prompts the user with structured onboarding questions.
.claude/skills/slack-notifier/├── SKILL.md├── config.json ← created on first run└── scripts/ └── send-notification.py// config.json — created during onboarding{ "webhook_url": "https://hooks.slack.com/services/...", "default_channel": "#eng-alerts", "mention_on_failure": "@oncall"}In SKILL.md:
## Setup
On first use, check for `config.json` in this skill directory.If it does not exist, ask the user:1. What is your Slack webhook URL?2. What channel should notifications go to?3. Who should be mentioned on failures?
Write their answers to `config.json` and confirm setup is complete.This makes the skill self-documenting and portable. Anyone who installs it gets a guided setup experience rather than a confusing failure.
7. Memory and Data Persistence
Skills that need to track state across invocations must choose the right persistence mechanism and the right location.
Choose the Right Format
| Use Case | Format |
|---|---|
| Audit trail, session log | Append-only text or JSONL |
| User preferences, config | JSON file |
| Task state, progress | JSON file |
| Complex queries, relationships | SQLite |
Use CLAUDE_PLUGIN_DATA, Not the Skill Directory
## Persistence
Store all runtime data in `${CLAUDE_PLUGIN_DATA}/skill-name/`.Never write runtime data to the skill directory itself.The skill directory is treated as read-only during execution. It may be overwritten during upgrades or reinstallation. Any data written to the skill dir risks being deleted when the skill updates.
${CLAUDE_PLUGIN_DATA} is a stable location outside the skill directory, specifically designed for persistent skill data. Use it for everything the skill writes at runtime.
8. Store Scripts, Don’t Reconstruct Them
Every time Claude generates the same boilerplate from scratch, it is burning context and introducing inconsistency. Pre-built scripts in the scripts/ subdirectory eliminate reconstruction.
.claude/skills/api-client/├── SKILL.md└── scripts/ ├── auth-wrapper.ts ← auth boilerplate, ready to compose ├── retry-logic.ts ← standard retry with exponential backoff ├── validate-response.ts ← schema validation helper └── error-types.ts ← shared error class hierarchyIn SKILL.md:
## Scripts
Pre-built utilities are in `scripts/`. Use them directly:- `auth-wrapper.ts` — wraps any fetch call with token refresh- `retry-logic.ts` — exponential backoff with jitter, configurable attempts- `validate-response.ts` — validates response against provided Zod schema
Do not reimplement these. Compose them.Claude’s job becomes decision-making and composition, not boilerplate generation. The scripts are deterministic and testable. They produce the same output every invocation. Claude producing them from scratch does not.
9. On-Demand Hooks for Opinionated Behavior
Hooks that run on every action create overhead and interruption. Opinionated behaviors — enforcing naming conventions, blocking certain commands, restricting edit scope — should be gated behind user-invocable triggers rather than running constantly.
Pattern:
## Hooks
This skill exposes two triggers:
`/careful` — activates strict mode: - Blocks `git push --force` regardless of arguments - Requires confirmation before any file deletion - Restricts writes to `src/` only
`/careful off` — deactivates strict mode and removes all restrictions.## Hooks
`/sprint-naming` — enforces naming conventions for the current sprint: - Component files must follow `[Feature][Type].tsx` pattern - Test files must be co-located with `*.test.tsx` suffix - No new files in `legacy/` without explicit comment
`/sprint-naming off` — removes naming enforcement.The hook fires when the trigger is active, stays silent otherwise. This keeps normal sessions uninterrupted while giving users an explicit on/off lever for behaviors they want during focused work.
Skill Quality Checklist
| Check | Pass Condition |
|---|---|
| Description is a trigger | Reads as “Use when…” |
| Gotchas section exists | At least 3 real edge cases documented |
| Main file under 200 lines | Heavy content lives in subdirs |
| No obvious instructions | Only non-default behaviors specified |
| Goals not steps | Constraints given, not procedures |
Persistent data in CLAUDE_PLUGIN_DATA | Not written to skill dir |
| Scripts pre-built | No “generate this boilerplate” instructions |
| Hooks are on-demand | Opinionated behavior is user-triggered |
5 Common Skill Anti-Patterns
1. Giant Monolithic SKILL.md
Problem: A 600-line SKILL.md loads entirely on every invocation. Most of it is irrelevant to any given task.
Fix: Keep SKILL.md under 200 lines. Move reference material, examples, and large tables into references/ subdirs. Claude discovers them when needed.
2. Step-by-Step Procedures
Problem: Numbered steps produce rigid, mechanical behavior. When step 3 is blocked, Claude either fails or skips it — both are wrong.
Fix: Express the goal and constraints. Let Claude determine sequencing. Steps are appropriate only for truly ordered operations (install → configure → start), not for open-ended development tasks.
3. Missing Gotchas Section
Problem: The skill works for the happy path but keeps producing the same wrong output for edge cases. Every time you catch the failure, it is a surprise.
Fix: Add a Gotchas section after the first real failure. Update it every time you catch a recurrence. The section becomes the institutional memory for how this skill fails.
4. Description Written as a Summary
Problem: "This skill helps with API design." — Claude reads this and does not know when to invoke it. It either invokes it too broadly or misses relevant cases.
Fix: Rewrite as a trigger condition. "Use when the user is designing a new API endpoint, reviewing an existing API contract, or asking about REST vs GraphQL trade-offs."
5. Runtime State Stored in the Skill Directory
Problem: The skill writes logs, preferences, and state to its own directory. When the skill is updated or reinstalled, that data is wiped.
Fix: All runtime writes go to ${CLAUDE_PLUGIN_DATA}/skill-name/. The skill directory is read-only during execution.