Error codes
Every diagnostic wisp can emit, what triggers it, and how to resolve it.
Every wisp diagnostic carries a stable code. They're grouped by family, and the family tells you which stage of the pipeline rejected your build.
| Family | Meaning | Raised by |
|---|---|---|
WISP-CONFIG | A primitive was declared in a way wisp can't read statically | Analyse |
WISP-SCOPE | A primitive was constructed somewhere it can't be found | Analyse |
WISP-DUP | Two resources share a name | Validate |
WISP-IAM | A generated policy isn't narrow enough to ship | Emit |
WISP-SHARD | Information about splitting into nested stacks | Emit |
WISP-AON | A resource would cost money while idle | Gate |
WISP-CONFIG
These mean wisp read your declaration and couldn't turn it into a resource. Almost always the fix is to inline a literal value instead of computing one, because wisp never runs your code while compiling.
WISP-CONFIG-001
The most common one. A primitive's name, path, options object, or handler wasn't something wisp could read statically.
// Rejected — options are computed at runtime.
export const orders = new Store("orders", buildOptions());
// Accepted.
export const orders = new Store("orders", {
partitionKey: { name: "id", type: "S" },
});The same rule covers route paths ("/orders/{id}" must be a literal), handlers (must be written inline, not
passed by reference), and .override() / addRawResource() arguments.
WISP-CONFIG-002
A queue already has a consumer. A Queue can have at most one — split the work inside your handler, or use a
second queue.
WISP-CONFIG-003
.override() was chained onto a route, consumer, schedule, or bucket handler. Per-handler overrides aren't
supported; call .override() on the standalone Api, Store, Queue, Schedule, or Bucket instead. See
escape hatches.
WISP-CONFIG-004
An addRawResource() logical id collides with one wisp generates. Pick a different id.
WISP-CONFIG-005
Two ctx.step() or ctx.sleep() calls in the same Workflow share a name. Step
names are how a workflow finds its place after a restart, so they have to be unique.
WISP-CONFIG-006
A ctx.step() or ctx.sleep() call is nested inside an if, loop, try, or switch. Workflow steps must be a
flat, sequential list at the top level of the handler. Ordinary code can go anywhere between them.
WISP-CONFIG-007
A workflow used ctx.parallel(), ctx.map(), or waitForCallback(). These aren't implemented yet — today's
Workflow supports ctx.step and ctx.sleep.
WISP-SCOPE
WISP-SCOPE-001
A primitive was constructed somewhere other than the top level of a module — inside a function, a conditional, or a loop. wisp has to be able to enumerate every resource without executing anything, so construction has to happen where it can be seen:
// Rejected.
function setup() {
return new Store("orders", { partitionKey: { name: "id", type: "S" } });
}
// Accepted.
export const orders = new Store("orders", {
partitionKey: { name: "id", type: "S" },
});WISP-DUP
WISP-DUP-001
Two resources of the same kind share a name. The message includes where the first one was declared. Names become part of the physical AWS resource name, so they have to be unique within a stage.
WISP-IAM
These are internal safety checks on the policies wisp itself generates. Seeing one usually means an escape hatch introduced something too broad.
WISP-IAM-001
A policy statement uses Resource: "*". Every statement must name a specific ARN.
WISP-IAM-002
A policy statement uses a wildcard action. Every action must be named explicitly.
WISP-IAM-003
A DynamoDB table isn't on PAY_PER_REQUEST billing. Provisioned capacity bills whether or not anyone reads or
writes, which breaks the $0 idle guarantee.
WISP-SHARD
WISP-SHARD-001
A warning, not an error. Your app was large enough to be split into nested stacks. Nothing is required of you — see stack sharding for what changes.
WISP-AON
The always-on family. Each code identifies a resource type that costs money with zero traffic, and each one blocks the deploy. The full table — every code, its monthly idle cost, and the $0 alternative — lives in the always-on denylist.
| Code | Detects |
|---|---|
WISP-AON-001 | RDS instance |
WISP-AON-002 | RDS cluster |
WISP-AON-003 | NAT gateway |
WISP-AON-004 | Application/network load balancer |
WISP-AON-005 | ElastiCache cluster |
WISP-AON-006 | ECS service |
WISP-AON-007 | OpenSearch domain |
WISP-AON-008 | MSK (Kafka) cluster |
WISP-AON-009 | Lambda provisioned concurrency |
WISP-AON-010 | Lambda attached to a VPC (requires a NAT gateway for egress) |
WISP-AON-011 | EC2 instance |
WISP-AON-012 | DynamoDB table with provisioned throughput |
Unlike every other family, these can be deliberately allowed:
export default {
app: "my-app",
region: "eu-north-1",
allowAlwaysOn: ["WISP-AON-003"],
};Doing so is loud rather than silent: the violation is still printed on every build, and wisp estimate reports the
guarantee as broken.

