Skip to content

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.py

Claude 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 structure
2. Identify files that need changes
3. List the changes required
4. Implement changes in order
5. Write tests for each change
6. Update documentation

Good:

Your goal is a clean, working implementation with test coverage.
Constraints: do not change the public API surface, keep files under
200 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 CaseFormat
Audit trail, session logAppend-only text or JSONL
User preferences, configJSON file
Task state, progressJSON file
Complex queries, relationshipsSQLite

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 hierarchy

In 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

CheckPass Condition
Description is a triggerReads as “Use when…”
Gotchas section existsAt least 3 real edge cases documented
Main file under 200 linesHeavy content lives in subdirs
No obvious instructionsOnly non-default behaviors specified
Goals not stepsConstraints given, not procedures
Persistent data in CLAUDE_PLUGIN_DATANot written to skill dir
Scripts pre-builtNo “generate this boilerplate” instructions
Hooks are on-demandOpinionated 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.