Writing Feature Specs That Claude Code Can Actually Use
Vague specs produce vague code. Here's what a usable spec includes — and what to leave out.
The single biggest predictor of Claude Code output quality is the quality of the spec you give it. Not prompt length, not examples, not conversation history. The spec.
Here's what makes a spec that produces good code on the first pass.
State the behavior, not the implementation
Bad spec: "Create a caching layer using Redis."
Good spec: "Cache the result of getUserProfile() for 5 minutes. Use the userId as the cache key. If the cache is warm, return the cached value without hitting the database. If the cache is cold or expired, fetch from the database and populate the cache."
The bad spec tells Claude what to use. The good spec tells Claude what to do. Claude knows how to use Redis — what it doesn't know is your specific caching requirements.
Specify the failure cases
Most bugs live in the failure cases, not the happy path. A good spec includes:
- What should happen when the primary operation fails
- What to do with invalid input
- What the caller should receive if the operation can't complete
"If the cache is unavailable, fall through to the database. If the database is also unavailable, throw a CacheError with the original database error attached." That's a real spec. "Handle errors appropriately" is not.
Include the context Claude doesn't have
Claude doesn't know:
- Which other parts of your codebase call this function
- What performance constraints apply
- What conventions you use for this type of code
- What you've already tried that didn't work
Add a short context block at the top of your spec: "This is called on every API request, so performance matters. We use async/await throughout, no callbacks. The existing cache module is at lib/cache.ts."
Scope what you don't want
Explicit exclusions prevent scope creep. "Do not add logging — the calling function handles that." "Do not modify the existing getUserProfile function signature." "Do not add retry logic — that's handled at the HTTP layer."
Without these, Claude will sometimes add things it thinks are helpful. They're usually fine. Occasionally they conflict with something you haven't shown it.
The format that works for me
## What it does
[1-3 sentences on the function's purpose]
## Inputs
[What it receives, including types and constraints]
## Outputs
[What it returns in each case: success, not found, error]
## Failure handling
[What happens when things go wrong]
## Context
[What calls this, what patterns to follow, what to avoid]
## Out of scope
[Explicit exclusions]
This format takes five minutes to write and saves 30 minutes of revision cycles. The first output from Claude is usually deployable.