design · rainmanjam — Claude Code skill: plan validation and failure behaviour
A skill for shaping a new interface together with its failure paths: the accepted range of every parameter, the checks, and what callers get when something breaks.
npx skills add rainmanjam/poka-yoke --skill design -a claude-codeWhat design does
The skill kicks in while you are still designing an interface, module, schema or type. A type annotation proves little. Write amount: float and negative values, infinity and sub-cent precision all get through, so the skill asks you to spell out the accepted domain for every parameter and enforce it at the top of the function. Each guard should name the value it rejected, which saves the caller a trip into the source.
Next comes the failure contract. Pick one style per module: raise when the caller cannot go on, return an outcome when a decision is theirs to make, substitute a default when the value is optional. Three conventions side by side inside one file force everyone to memorise which function does which, and that memory becomes its own bug source. Validation is meant to appear twice on purpose — a user-facing message at the entry point, a programmer-facing one deeper in, because someone will add a second entry point later.
Schemas get the same treatment: required fields stay non-nullable, constraints are repeated at the storage layer so a migration or console session cannot slip past them, and you decide up front how readers treat rows that predate the current rules. Outbound calls need bounds. A network call without a timeout does not fail, it hangs, and a missing size ceiling on an upload lets a caller eat memory. Put the unit in the parameter name: timeout_seconds, max_rows, retry_limit.
What it helps with
- write down the accepted values of each parameter before callers exist
- choose raise, outcome or default for every way the operation can fail
- place guards at the entry point and repeat them deeper in the module
- make required schema fields non-nullable and mirror constraints in storage
- set timeouts, size ceilings and a retry cap on outbound calls
Example prompts for the agent
- Design a
chargefunction and state which values ofamount,currencyandcustomer_idpass and which get rejected - Write the failure contract for a new orders module: what raises, what returns an outcome
- Work out validation for the payments table and decide what happens to old rows that skip it
How to install design
1Skills CLI (Vercel)
npx skills add rainmanjam/poka-yoke --skill design -a claude-code2Manually: put the files in place
~/.claude/skills/design/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/design/
1Skills CLI (Vercel)
npx skills add rainmanjam/poka-yoke --skill design -a codex2Manually: put the files in place
.agents/skills/design/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add rainmanjam/poka-yoke --skill design -a cursor2Manually: put the files in place
.cursor/skills/design/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install rainmanjam/poka-yoke design2Manually: put the files in place
.github/skills/design/SKILL.md1Skills CLI (Vercel)
npx skills add rainmanjam/poka-yoke --skill design -a gemini-cli2Platform CLI
gemini skills install https://github.com/rainmanjam/poka-yoke --path benchmarks/controls/defensive/skills/designManually: put the files in place
.devin/skills/design/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add rainmanjam/poka-yoke --skill design -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
Step by step, with commands for each platform: How to install skills in Claude Code: five ways
Questions about design
How do I install the design skill in Claude Code?
Run npx skills add rainmanjam/poka-yoke --skill design -a claude-code. The skill lands at ~/.claude/skills/design/SKILL.md.
What does the design skill do?
It guides you through specifying a new interface along with its validation and failure behaviour. You end up with a signature and its accepted domain, the guard checks, and a stated failure contract.
Why validate twice in the same module?
A lone check is a single point of failure. The inner guard holds the line when a second path into the function shows up later, and keeping it separate lets the outer error stay friendly to whoever sent the request.
Source text
SKILL.md · MIT
---
name: design
description: >-
Designing a new interface, module, schema or type together with its validation and failure
behaviour: what each input accepts, what happens when it does not, and what the caller gets
back when something goes wrong.
---
# Design: Decide the Failure Behaviour Now
A new interface is a chance to decide what happens when things go wrong *before* anything
depends on the answer. Retrofitting validation onto an interface that has callers means
choosing between breaking them and leaving the gap open.
So design the failure paths at the same time as the happy path. For every parameter, decide
what it accepts and what it does with everything else. For every operation that can fail,
decide what the caller receives.
## Specify the accepted domain of every parameter
A type is a start, not a specification. `amount: float` still permits negatives, infinity, NaN
and values with more precision than money has. Write down what is actually allowed:
```python
def charge(amount, currency, customer_id):
"""
amount: positive, at most 2 decimal places, below the 10_000 per-txn ceiling
currency: 3-letter ISO code, one of the supported set
customer_id: non-empty, exists, account not closed
"""
```
Then enforce it at the top of the function, because a docstring is not a check:
```python
if not isinstance(amount, (int, float)) or amount <= 0:
raise ValueError(f"amount must be positive, got {amount!r}")
if round(amount, 2) != amount:
raise ValueError(f"amount has sub-cent precision: {amount!r}")
if currency not in SUPPORTED_CURRENCIES:
raise ValueError(f"unsupported currency {currency!r}")
```
Three checks, each naming the value that failed. A caller reading the error knows what to fix
without opening the source.
## Decide the failure contract before implementing
For each way the operation can fail, choose one and be consistent:
**Raise.** The caller cannot sensibly continue and should not have to check. Right for
programmer error and for corrupt state.
**Return an outcome.** The failure is expected and the caller has a decision to make. Right for
"not found", "already exists", "insufficient funds".
**Return a default.** The value is optional and a sensible substitute exists. Right for display
paths and configuration, wrong for anything that moves money or deletes data.
Mixing all three inside one module means every caller has to remember which convention this
function follows, so the convention itself becomes a source of mistakes.
## Validate at the boundary, and again at depth
The entry point should reject bad input early, with a message aimed at whoever sent it. Inner
functions should check again, because a future caller may reach them by another path.
This duplication is deliberate. A single check is a single point of failure, and the inner
check is what protects you when someone adds a second entry point next year and forgets.
Keep the two different in emphasis: the outer check produces a user-facing error, the inner
check produces a programmer-facing one.
## Schemas and stored shapes
**Make required fields non-nullable and say so.** A column that permits null will eventually
contain null, and every reader must handle it.
**Constrain at the storage layer too.** Check constraints, foreign keys and unique indexes hold
when application code is bypassed by a migration, a script or a console session.
**Decide what a missing optional means.** Absent and empty are different, and if the difference
matters, the schema should distinguish them rather than leaving each consumer to guess.
**Plan for the malformed row.** Data that predates the current validation exists in every
system with history. Decide whether readers skip it, repair it, or fail on it.
## Timeouts, limits and resource ceilings
Anything that talks to something else needs a bound, decided at design time:
- **Timeout on every network call.** A call without one waits forever, and the failure surfaces as a hang rather than an error.
- **Size ceiling on every input you accept.** Uploads, request bodies, list parameters and pagination limits all need a maximum, or a caller can exhaust memory.
- **Retry policy, with a cap.** Decide how many, how spaced, and which errors are worth retrying. Retrying a validation failure just fails more slowly.
- **A ceiling on anything recursive or iterative** driven by input, so a malformed structure cannot loop indefinitely.
Name the unit in the parameter: `timeout_seconds`, `max_rows`, `retry_limit`.
## What the caller receives when it fails
An error is an interface too, and it deserves the same care as the success path.
- **Say what was wrong, specifically.** "Invalid request" tells the caller nothing; "currency 'XYZ' is not supported" tells them exactly what to change.
- **Include the offending value**, truncated if it might be large, and never if it might be a secret.
- **Distinguish caller error from system failure.** The first is theirs to fix, the second is yours, and conflating them wastes everyone's time.
- **Keep the error stable.** Callers will parse it, whatever you intended.
## What good output looks like
- **Show the signature with its accepted domain**, not just its types.
- **Show the guards.** The checks are the design, so put them on the page.
- **State the failure contract explicitly**: what raises, what returns an outcome, what defaults.
- **Cover the dependency being unavailable**, not only the inputs being wrong.
## What to avoid
**Types treated as validation.** `amount: float` does not exclude negative, infinite or
sub-cent values. The annotation documents intent; the check enforces it.
**Errors that lose the cause.** Catching and re-raising a generic exception discards the
information the caller needed.
**Validation that cannot be reached.** A check after the value has already been used is
decoration.
**Unbounded anything.** No timeout, no size limit, no retry cap. Each is a defect waiting for
an unusual day.
© rainmanjam · MIT · rainmanjam/poka-yoke
Similar skills
All skills for Backend and APIs →design by other authors comes first.