Errors
From errors.md, the same document your agent reads.
Every error the platform, the edge, the git hooks and the CLI produce has a stable code. The wire format is the same everywhere:
{
"error": {
"code": "SECRET_IN_COMMIT",
"message": "A value that looks like a Stripe secret key is in src/config.ts line 12.",
"fix": "Remove the value, declare STRIPE_SECRET_KEY under secrets in whisk.yaml, read it from the environment, and ask an owner to set it at https://whisk.run/o/acme/secrets.",
"docs": "https://skill.whisk.run/errors/SECRET_IN_COMMIT",
"details": { "file": "src/config.ts", "line": 12, "rule": "stripe-secret" }
}
}code is what an agent branches on. message says what happened, in one sentence, with the specific names involved. fix says what to do next, in imperative sentences an agent can follow without reading anything else. docs links to the section below. details is structured and differs by code. Codes are upper snake case and never reused for a different meaning; new codes are additive within a conventions version.
Each section states the HTTP status where one applies (- for codes that arrive in a deploy record, a notification or CLI output rather than an HTTP response), when it occurs, the fix text, and an example. Surface names where it comes from: api, edge (an app hostname), hooks (the webhook ingress), auth (the sign-in host), git (pre-receive), cli, deploy (a deploy or build record), run (a workflow run), container (whisk-init, in the app's own log), node (the node agent's answer to the control plane, which lands in the deploy record and the operator's view).
AUTH_REQUIRED
Status: 401 · Surface: api, edge
When: no credential was presented. On the API, no bearer token or session. On an app hostname, a private route was fetched without a session, or a service route (queue.endpoint, a webhook handler) was requested from the internet.
Fix: For the API, run whisk login and retry. For an app route, sign in by navigating to /.whisk/login?return=<path> on the app, or make the route public in routes.public if it is meant for everyone.
{"error":{"code":"AUTH_REQUIRED","message":"This route requires a signed-in user.","fix":"Navigate to /.whisk/login?return=/reports to sign in, or add the route to routes.public in whisk.yaml if it is meant for everyone.","docs":"https://skill.whisk.run/errors/AUTH_REQUIRED","details":{"route":"/reports","audience":"anonymous"}}}TOKEN_EXPIRED
Status: 401 · Surface: api, git
When: the agent, deploy or service token has passed its expiry.
Fix: Run whisk login to obtain a fresh agent token. For CI, create a new token with whisk agent-token create and update the WHISK_TOKEN secret in the CI system.
{"error":{"code":"TOKEN_EXPIRED","message":"The agent token expired on 2026-10-07T09:12:00Z.","fix":"Run whisk login to obtain a new token.","docs":"https://skill.whisk.run/errors/TOKEN_EXPIRED","details":{"expired_at":"2026-10-07T09:12:00Z","kind":"agent"}}}TOKEN_REVOKED
Status: 401 · Surface: api, git
When: a human revoked the token from the dashboard, or the person it acted for left the org.
Fix: Run whisk login. If it was revoked deliberately, ask an owner or admin of the org why before continuing.
{"error":{"code":"TOKEN_REVOKED","message":"This token was revoked by ana@acme.example on 2026-09-30T14:02:11Z.","fix":"Run whisk login to obtain a new token, and ask ana@acme.example whether the revocation was intended.","docs":"https://skill.whisk.run/errors/TOKEN_REVOKED","details":{"revoked_at":"2026-09-30T14:02:11Z","revoked_by":"ana@acme.example"}}}TOKEN_SCOPE
Status: 403 · Surface: api
When: the token is valid but not scoped to this org, this app, or this action. An agent token does only what its grant scopes allow, even when the person it acts for may do more: deploying, creating or changing apps and restoring or opening a database need deploy, declaring secrets needs secrets:declare, and reading logs and error groups needs logs:read. details.required names the missing scope and details.token_scopes lists the token's own. Agent tokens never carry secrets:read, so reading a secret value always fails with this code. An operator read token (operator:read) only reads: any other method, and any route outside the operator reads and its person's org reads, answers this code. The one write it may make is setting a piece of feedback's status, and only when it also carries feedback:resolve; without it, that write answers this code with details.required feedback:resolve. An app's own token sending an event is refused with this code when it is tied to no deploy the platform knows, and a workflow registration from an app's SDK is refused with it when it names another app or any URL, at the top or on a step, other than the app's serve URL (details.field names the first such field, details.url the serve URL).
Fix: Use the org and app the token was issued for (whisk whoami shows them), or ask a human to create a token with the needed scope with whisk agent-token create --scopes, or run whisk login, which grants deploy, logs:read and secrets:declare. Send events with the WHISK_SERVICE_TOKEN the container received, and leave the workflow SDK's serve origin and path unset so it registers at the URL the platform gives it. Secret values can never be read; declare the name and let a human set it. With an operator read token, make the change signed in, or with an agent token from whisk login; to resolve feedback with one, ask the operator for a read token created with feedback:resolve on the operator page.
{"error":{"code":"TOKEN_SCOPE","message":"This token is scoped to app job-tracker and cannot act on app reporting-api.","fix":"Run whisk use acme/job-tracker, or ask an admin for a token scoped to reporting-api.","docs":"https://skill.whisk.run/errors/TOKEN_SCOPE","details":{"required":"app:01J9X4","token_scopes":["org:01J9W2","app:01J9X3","deploy"]}}}BUSINESSNOTIN_LOGIN
Status: 403 · Surface: api, git
When: a whisk login was used in a business it does not cover, though the person it acts for belongs to that business. A login covers the businesses it was approved for: a login for all the person's businesses covers the ones they belonged to when they approved it, and never one they joined later; a login for one business covers that business. A person can add a business to either without a new login. details.url is the page where they do it, details.org and details.org_name name the business, details.login is the login's id and details.device the computer it is bound to. The CLI prints this as NEEDS_HUMAN with the link.
Fix: Show the person details.url. They open it signed in, check the login is theirs by its computer and when it was made, and tap Add; then retry. No new login is needed. Do not run whisk login again for this.
{"error":{"code":"BUSINESS_NOT_IN_LOGIN","message":"This login does not cover Northwind Bakery.","fix":"Ask the person to open https://whisk.run/me/logins/01J9Y2?add=northwind, check the login is theirs and tap Add Northwind Bakery, then retry. No new login is needed.","docs":"https://skill.whisk.run/errors/BUSINESS_NOT_IN_LOGIN","details":{"org":"northwind","org_name":"Northwind Bakery","url":"https://whisk.run/me/logins/01J9Y2?add=northwind","login":"01J9Y2","device":"ana-laptop"}}}TOKEN_SIGNATURE
Status: 401 · Surface: api, git
When: the agent token is bound to the computer that ran whisk login, and the request did not prove it came from there. On the API, the Whisk-Signature header is missing, cannot be read, was made more than five minutes from the platform's clock, or does not match the request and the token's key: the token was copied to another computer, or sent by something other than the whisk CLI. On git, the bound token itself was given as the password, which git cannot sign. details.reason is missing, malformed, stale, mismatch or (git) git, and details.device names the computer the token belongs to.
Fix: Run whisk login again on this computer and use the whisk CLI or whisk mcp, which sign every request; never copy the token elsewhere. For stale, set this computer's clock to the right time. For git, use whisk deploy, or let whisk git-credential answer git (whisk clone sets it up). For CI, use a token from whisk agent-token create in WHISK_TOKEN, which is not bound.
{"error":{"code":"TOKEN_SIGNATURE","message":"This token is bound to the computer that signed in, and this request carries no Whisk-Signature.","fix":"Run whisk login again on this computer, and use the whisk CLI (or whisk mcp) rather than sending the token yourself.","docs":"https://skill.whisk.run/errors/TOKEN_SIGNATURE","details":{"reason":"missing","device":"ana-laptop"}}}FORBIDDEN_ROLE
Status: 403 · Surface: api, git
When: the person is signed in but their org role does not allow the action. The authorisation matrix is in the control plane specification: deploys need developer or above, secret values need admin or above, members and access need admin or above, billing needs owner or billing. An app's own service or container token has no org role: on a route that needs one it gets this code, a message naming the routes it can call (its queue, uploads, customers and email) and a fix pointing at POST $WHISK_QUEUE_URL for events. A deploy token only pushes to git. On git, an agent token whose person is not in the app's org, or whose role does not deploy (billing, member), is refused the push with this code, as the deploy route would refuse it.
Fix: Ask an owner or admin of the org to perform the action or to change your role. The message names the role that would be enough.
When the role allows the action and an agent token never may (changing billing, members, exports), the message says it is the token's limit, details.token is agent, and the fix points at the dashboard, where the person does it signed in. An agent acting for an owner or billing contact reads billing (whisk billing).
{"error":{"code":"FORBIDDEN_ROLE","message":"Setting a secret value needs the admin or owner role; you are a developer.","fix":"Ask an owner or admin of acme to set STRIPE_SECRET_KEY at https://whisk.run/o/acme/secrets, or to change your role.","docs":"https://skill.whisk.run/errors/FORBIDDEN_ROLE","details":{"required_any":["owner","admin"],"role":"developer"}}}AGENT_CATEGORY
Status: 403 · Surface: api
When: an agent identity (a coding agent's own standing on the platform, acting for one person) called a route in a category of change it does not hold. Every route is in one category: read, operate (deploy, re-run, cancel, resolve feedback, take a restore point), change (add or change records), destroy (replace or remove something the platform can undo) or human. human covers who can do what, money, secret values, credentials and everything that cannot be undone, and no agent identity may ever hold it. Naming a code to run destructive SQL with whisk db query needs destroy. details.required is the category the route needs and details.categories the identity's own.
Fix: For human, ask your person to do it signed in at the dashboard; nothing makes an agent able to. For any other category, ask your person to add it to the identity on the operator page (whisk.run/operator, Agent identities). The change applies to your next request; your credential stays the same.
{"error":{"code":"AGENT_CATEGORY","message":"This agent identity does not hold the Destructive category.","fix":"Ask your person to add Destructive to the identity on the operator page, or to do this signed in.","docs":"https://skill.whisk.run/errors/AGENT_CATEGORY","details":{"required":"destroy","categories":["read","operate","change"]}}}AGENT_PAUSED
Status: 403 · Surface: api
When: the agent identity this credential belongs to is paused. Its person paused it on the operator page, and every request it makes is refused until they resume it.
Fix: Stop and tell your person the identity is paused; only they resume it, on the operator page. Do not look for another credential to carry on with.
{"error":{"code":"AGENT_PAUSED","message":"The agent identity this credential belongs to is paused.","fix":"Stop and tell your person; only they resume it, on the operator page at whisk.run/operator.","docs":"https://skill.whisk.run/errors/AGENT_PAUSED","details":{"identity":"Claude"}}}ORGPENDINGLIMIT
Status: 403 · Surface: api
When: the org has not been confirmed by a human yet. Pending orgs can deploy previews but cannot set custom domains, send email, deploy to production, or exceed free-tier limits.
Fix: Ask the human to confirm the account with a work email at the URL in details. Deploy a preview meanwhile with whisk deploy --env preview.
{"error":{"code":"ORG_PENDING_LIMIT","message":"Custom domains are not available until the account is confirmed.","fix":"Ask the account owner to confirm at https://whisk.run/welcome?org=acme, then retry. A preview deploy works now: whisk deploy --env preview.","docs":"https://skill.whisk.run/errors/ORG_PENDING_LIMIT","details":{"expires_at":"2026-09-09T10:00:00Z","confirm_url":"https://whisk.run/welcome?org=acme"}}}ORG_FROZEN
Status: 403 · Surface: api, git
When: the org is frozen: for non-payment, by an operator, or because nobody used a free org for 90 days. Apps serve a maintenance page, deploys are refused, export still works.
Fix: For non-payment, ask the billing contact to update the payment method at the billing URL. For inactivity, any member signing in to the dashboard, or a member's agent calling the API or pushing, wakes the org first, so this is answered only to an app's visitors. Export is available at any time with whisk export.
{"error":{"code":"ORG_FROZEN","message":"acme is frozen because the invoice for August is unpaid.","fix":"Ask the billing contact to pay at https://whisk.run/o/acme/billing. Apps resume within a minute of payment. whisk export works while frozen.","docs":"https://skill.whisk.run/errors/ORG_FROZEN","details":{"frozen_at":"2026-09-01T00:00:00Z","shred_at":"2026-10-01T00:00:00Z"}}}ORGSTATUSREFUSED
Status: 409 · Surface: api
When: the business's status does not allow the change asked for: restoring a business that is not being deleted, restoring one being deleted because a payment failed or by Whisk, freezing one that is being deleted, or anything on a business that is already shredded.
Fix: Read the message: a business deleted for a failed payment is restored by paying what is owed on the billing page; one deleted by Whisk only through support.
{"error":{"code":"ORG_STATUS_REFUSED","message":"This business is being deleted because a payment failed.","fix":"Pay what is owed on the billing page; that restores it.","docs":"https://skill.whisk.run/errors/ORG_STATUS_REFUSED","details":{"status":"shredding"}}}ADDRESS_BLOCKED
Status: 403 · Surface: edge
When: something on the request's network address asked three times within ten minutes for paths only an attacker's scanner asks for (WordPress, PHP, secrets or version control files such as /.env and .git), or with a known attack tool's user agent, so the edge is not answering requests from it on any Whisk address for a while: an hour the first time, a day if it does so again within 30 days, a week after that, and never more than an hour for an address people are seen using. A browser opening a page from that address is shown a short browser check instead (the page carries this code) and, once it passes, gets through for a day. Shared and private networks' own addresses, and coding agents' shared outbound addresses, are never blocked.
Fix: Open the page in a browser, which passes a short check; otherwise try again later or use another network. Never request those paths.
{"error":{"code":"ADDRESS_BLOCKED","message":"This network address is blocked for a while because something on it asked for addresses only attackers ask for.","fix":"Open the page in a browser, which passes a short check; otherwise try again later or use another network.","docs":"https://skill.whisk.run/errors/ADDRESS_BLOCKED","details":{}}}ORGUNDERREVIEW
Status: 403 · Surface: api, git, edge, hooks, deploy
When: a check found an app of the business that looks like a scam or malware, or the business was started by a person, address or computer already held, so it is paused until Whisk reviews it (CONTROL-PLANE.md §6.27). Apps show a review page; deploys, new apps, previews, app email and webhooks are refused. Export still works.
Fix: Nothing to do: Whisk reviews every held business and releases it if the check was wrong. Write to support@whisk.run if it is urgent.
{"error":{"code":"ORG_UNDER_REVIEW","message":"acme is paused while Whisk reviews it.","fix":"Nothing to do: Whisk reviews every held business and releases it if the check was wrong. Write to support@whisk.run if it is urgent.","docs":"https://skill.whisk.run/errors/ORG_UNDER_REVIEW","details":{"org":"acme"}}}ACCOUNTLASTOWNER
Status: 409 · Surface: api, cli
When: a person asked to delete their own account (DELETE /v1/me, whisk account delete) while they are the only active owner of one or more businesses, which would be left with nobody in charge. details.orgs names each business, with its People and settings pages.
Fix: For each business named, make someone else an owner on its People page, or delete the business in its settings, then delete your account again.
{"error":{"code":"ACCOUNT_LAST_OWNER","message":"You are the only owner of Acme (acme), so deleting your account would leave it with nobody in charge.","fix":"For each business named, make someone else an owner on its People page, or delete the business in its settings, then delete your account again.","docs":"https://skill.whisk.run/errors/ACCOUNT_LAST_OWNER","details":{"orgs":[{"org":"acme","name":"Acme","people_url":"https://whisk.run/o/acme/people","settings_url":"https://whisk.run/o/acme/settings"}]}}}PLANLIMITAPPS
Status: 402 · Surface: api
When: creating another app would exceed the plan's app count.
Fix: Delete an unused app with whisk apps delete, or ask an owner to upgrade the plan at the billing URL.
{"error":{"code":"PLAN_LIMIT_APPS","message":"The Team plan allows 10 apps and acme has 10.","fix":"Delete an app you no longer need with whisk apps delete <slug>, or ask an owner to upgrade at https://whisk.run/o/acme/billing.","docs":"https://skill.whisk.run/errors/PLAN_LIMIT_APPS","details":{"limit":10,"current":10,"plan":"team"}}}FREEAPPTAKEN
Status: 402 · Surface: api, git, cli
When: the first production deploy of an app in a business on the Free plan that has no free app. Every person gets one free app, for the first business they set up, and every work email domain gets one, for the first business confirmed with it. A further business can be created and can deploy previews, but publishes only on a paid plan or during its Starter trial. details.held_by names the business that has the free app. A push to the default branch that would be that deploy is refused at pre-receive with this error, so whisk deploy fails at once.
Fix: Ask an owner to start the free Starter trial or choose a paid plan on the business's billing page. Preview deployments keep working meanwhile.
{"error":{"code":"FREE_APP_TAKEN","message":"Acme Labs has no free app: each person and each work email domain gets one, and Acme Ltd already has it.","fix":"Start the free Starter trial or choose a paid plan for Acme Labs on its billing page. Preview deployments remain available.","docs":"https://skill.whisk.run/errors/FREE_APP_TAKEN","details":{"held_by":"Acme Ltd"}}}PLANLIMITMEMBERS
Status: 402 · Surface: api
When: inviting another member or guest would exceed the plan's seat count.
Fix: Remove someone who has left, or ask an owner to upgrade the plan.
{"error":{"code":"PLAN_LIMIT_MEMBERS","message":"The Team plan allows 25 people and acme has 25.","fix":"Remove someone who has left with whisk members remove <email>, or ask an owner to upgrade at https://whisk.run/o/acme/billing.","docs":"https://skill.whisk.run/errors/PLAN_LIMIT_MEMBERS","details":{"limit":25,"current":25,"plan":"team"}}}PLANLIMITPREVIEWS
Status: 402 · Surface: api, cli
When: starting a preview for a branch would give the app more previews than the plan allows: one per app on the free app, three per app on every paid plan. A branch that already has a preview never counts against it. details.previews names the app's previews.
Fix: Remove a preview nobody needs with whisk envs delete preview:<branch> (or delete its branch), or wait for one to expire; on the free app, an owner can choose a paid plan for three.
{"error":{"code":"PLAN_LIMIT_PREVIEWS","message":"crm already has 1 preview, the most the Free plan allows for one app.","fix":"Remove one with whisk envs delete preview:<branch> (or delete its branch), then start this one again. Paid plans allow 3 previews per app.","docs":"https://skill.whisk.run/errors/PLAN_LIMIT_PREVIEWS","details":{"limit":1,"plan":"free","previews":["preview:new-invoice-screen"]}}}PRODUCTIONNEEDSMAIN
Status: 409 · Surface: api, cli
When: a deploy to production named a branch other than main (or master), or a commit or build that the app's default branch does not contain. Only the default branch goes live; any other branch runs as a preview at its own address. Returning to one of production's own earlier deploys (a rollback) is always allowed.
Fix: Merge the branch into main, then run whisk deploy from main. To show the branch without putting it live, run whisk deploy from the branch, which starts its preview.
{"error":{"code":"PRODUCTION_NEEDS_MAIN","message":"Only main goes live; new-invoice-screen is a branch.","fix":"Merge new-invoice-screen into main, then run whisk deploy from main. To show it without putting it live, run whisk deploy from new-invoice-screen for a preview.","docs":"https://skill.whisk.run/errors/PRODUCTION_NEEDS_MAIN","details":{"branch":"new-invoice-screen"}}}PREVIEWNOWORKFLOWS
Status: 409 · Surface: api
When: a preview's container called the workflow API: sending an event, registering functions or recording a run's steps. Previews run no workflows, scheduled functions or events, so a branch's code never starts production's runs or replaces its functions.
Fix: Test functions locally with whisk dev, or merge the branch into main and deploy it when it is ready. In code, skip sending events when WHISK_ENV starts with preview:.
{"error":{"code":"PREVIEW_NO_WORKFLOWS","message":"Previews run no workflows, scheduled functions or events; this call was not sent.","fix":"Test functions locally with whisk dev, or merge the branch into main and deploy it. Skip sending events when WHISK_ENV starts with preview:.","docs":"https://skill.whisk.run/errors/PREVIEW_NO_WORKFLOWS","details":{"environment":"preview:new-invoice-screen"}}}PLANLIMITRUNS
Status: 402 · Surface: api, run
When: the org has used its monthly workflow run allowance on a plan without run overage. The event is kept, not lost: the platform holds it and delivers it, in order, when the month turns or the plan changes, and the answer is 402 so the sender knows its run has not started. A scheduled function is started by the engine on its own clock and is counted but never held.
Fix: Ask an owner to upgrade or to enable overage at the billing URL. Reduce runs by batching events or removing a chatty cron.
{"error":{"code":"PLAN_LIMIT_RUNS","message":"acme has used 50,000 of 50,000 runs this month; new runs are queued.","fix":"Ask an owner to enable overage or upgrade at https://whisk.run/o/acme/billing. Runs resume on 2026-10-01T00:00:00Z otherwise.","docs":"https://skill.whisk.run/errors/PLAN_LIMIT_RUNS","details":{"limit":50000,"used":50000,"resets_at":"2026-10-01T00:00:00Z"}}}PLANDOWNGRADEBLOCKED
Status: 409 · Surface: api, cli
When: an owner asked to move to a plan the org does not fit: more apps, members or storage than it includes, or a feature switched on that it does not include (single sign-on). details.over names each limit with what the org has and what the plan allows; details.features names each feature to switch off first (sso).
Fix: Delete what the smaller plan does not include and switch off the features it lacks (single sign-on in the business's settings), then change the plan again.
{"error":{"code":"PLAN_DOWNGRADE_BLOCKED","message":"acme has 12 apps and the Starter plan includes 10.","fix":"Delete 2 apps you no longer need with whisk apps delete <slug>, then change the plan again.","docs":"https://skill.whisk.run/errors/PLAN_DOWNGRADE_BLOCKED","details":{"plan":"starter","over":[{"limit":"apps","has":12,"allows":10}]}}}{"error":{"code":"PLAN_DOWNGRADE_BLOCKED","message":"Acme has single sign-on on and the Starter plan does not include it.","fix":"Turn single sign-on off at https://whisk.run/o/acme/settings (members then sign in with their own email), then change the plan again.","docs":"https://skill.whisk.run/errors/PLAN_DOWNGRADE_BLOCKED","details":{"plan":"starter","over":[],"features":["sso"]}}}PLAN_FEATURE
Status: 402 · Surface: api
When: the action needs a plan setting the org's plan does not include: a custom domain (custom_domains), keeping an app awake (always_on), or a manifest setting the plan lacks (storage, email, customer_identity), company sign-in (sso), forwarding logs to the org's own log service (log_forwarding), setting or changing the org's own storage bucket, which also receives the backup copy (own_bucket, Business only; sending the bucket the org already has again with new keys is allowed on any plan), checking an app's packages now (package_scanning) or serving an app through the CDN (cdn). details.setting names it and details.plan the plan. kv on a plan without it is not refused: the app deploys without a cache and W080 says so (doctor-rules.md).
Fix: Ask an owner to upgrade the plan at the billing URL, or stop using the setting.
{"error":{"code":"PLAN_FEATURE","message":"Custom domains are not included in the Free plan.","fix":"Ask an owner to upgrade at https://whisk.run/o/acme/billing.","docs":"https://skill.whisk.run/errors/PLAN_FEATURE","details":{"setting":"custom_domains","plan":"free"}}}TRIAL_USED
Status: 409 · Surface: api
When: an owner asked to start the free Starter trial for an org that has already had one, or had their own trial on another business. Each business gets one trial, ever, and so does each person, whether it ended by being cancelled, by a payment that did not go through, or by becoming a paid plan. details.trial_used_at is when the first one started; when it was the person's trial on another business, details.used_on names that business. Also the answer when an agency asks for its first client business's free month (trial: true) and has had it (details.client_trial_org names the client business that had it) or the person asking has had their one trial (details.used_on).
Fix: Choose a plan from the billing page instead, or add the client business without the free month: it is billed from the day it is added.
{"error":{"code":"TRIAL_USED","message":"acme has already had its free trial, started on 2 September 2026.","fix":"Choose a plan at https://whisk.run/o/acme/billing; each business gets one trial.","docs":"https://skill.whisk.run/errors/TRIAL_USED","details":{"trial_used_at":"2026-09-02T10:00:00Z"}}}DEMONOTFOUND
Status: 404 · Surface: api, dashboard
When: a demo business's handover link names a code no business has. The link was mistyped or cut short, or the business it named was deleted.
Fix: Ask the person who sent the link to copy it again from the operator page.
{"error":{"code":"DEMO_NOT_FOUND","message":"No business is waiting to be handed over at this link.","fix":"Ask the person who sent the link to copy it again.","docs":"https://skill.whisk.run/errors/DEMO_NOT_FOUND","details":{}}}DEMO_CLAIMED
Status: 409 · Surface: api, dashboard
When: someone tried to claim a demo business that has already been claimed. A demo business is handed over once; details.org is its slug.
Fix: The person who claimed it is its owner and can invite anyone else from the People page.
{"error":{"code":"DEMO_CLAIMED","message":"Harbour Building has already been claimed.","fix":"Ask its owner to invite you from https://whisk.run/o/harbour-building/people.","docs":"https://skill.whisk.run/errors/DEMO_CLAIMED","details":{"org":"harbour-building"}}}DEMOWRONGDOMAIN
Status: 403 · Surface: api, dashboard
When: a person signed in with an email outside the company's domain tried to claim a demo business made for that company. Only someone with an email at details.domain can claim it, so a demo cannot become a business for someone else.
Fix: Sign out and sign in again with your email at the company's domain.
{"error":{"code":"DEMO_WRONG_DOMAIN","message":"Harbour Building can only be claimed with an email at harbourbuilding.com.","fix":"Sign out, then sign in with your harbourbuilding.com email and open the link again.","docs":"https://skill.whisk.run/errors/DEMO_WRONG_DOMAIN","details":{"domain":"harbourbuilding.com"}}}HANDOVERWRONGEMAIL
Status: 403 · Surface: api, dashboard
When: a person tried to accept a client business an agency is handing over while signed in with an email other than the contact the agency named. Only details.contact can accept it, so a handover link cannot give the business to someone else.
Fix: Sign out and sign in again with the contact's email, or ask the agency to make the link for your email.
{"error":{"code":"HANDOVER_WRONG_EMAIL","message":"Harbour Building is being handed to sam@harbourbuilding.com.","fix":"Sign out, then sign in as sam@harbourbuilding.com and open the link again, or ask the agency to make the link for your email.","docs":"https://skill.whisk.run/errors/HANDOVER_WRONG_EMAIL","details":{"contact":"sam@harbourbuilding.com"}}}PAYOUTS_UNAVAILABLE
Status: 409 · Surface: api, dashboard
When: an agency asked to set up payouts for its rebate, and Whisk cannot pay out yet: payments are not configured, or Stripe would not make the payout account. Nothing is lost: the whole rebate comes off the agency's own bill as credit until payouts work.
Fix: Nothing to do now; the rebate keeps coming off your bill. Try again later, or contact support.
{"error":{"code":"PAYOUTS_UNAVAILABLE","message":"Payouts are not available yet.","fix":"Nothing to do now: your rebate comes off your Whisk bill as credit until payouts are available. Try again later, or contact support.","docs":"https://skill.whisk.run/errors/PAYOUTS_UNAVAILABLE","details":{}}}DEMO_UNCLAIMED
Status: 409 · Surface: api, dashboard
When: someone tried to choose a plan or start a trial for a demo business nobody has claimed yet. A demo runs on its one free app until its company claims it; the plan and the trial are theirs to choose.
Fix: Hand the business over first; the person who claims it chooses a plan.
{"error":{"code":"DEMO_UNCLAIMED","message":"Harbour Building is a demo nobody has claimed yet, so it stays on its free app.","fix":"Send the handover link from https://whisk.run/operator; the person who claims it chooses a plan.","docs":"https://skill.whisk.run/errors/DEMO_UNCLAIMED","details":{}}}DOCTOR_FAILED
Status: - · Surface: cli
When: whisk doctor found at least one error, or whisk deploy ran doctor first and it found one. details carries the whole report: errors, warnings and findings, each finding with its rule id, message, fix and, where it has one, the file and line. With --json the report is inside this one error object, not printed beside it. The CLI exits 3.
Fix: Fix each finding with an error severity; whisk doctor --fix applies the ones that are safe to apply. Then run whisk doctor again. whisk deploy --force pushes anyway, for when a finding is wrong about this app.
{"error":{"code":"DOCTOR_FAILED","message":"1 error(s) must be fixed before deploying.","fix":"Fix each finding (whisk doctor shows them; --fix applies the safe ones), or pass --force to push anyway.","docs":"https://skill.whisk.run/errors/DOCTOR_FAILED","details":{"errors":1,"warnings":0,"findings":[{"rule":"W001","severity":"error","message":"whisk.yaml is missing.","fix":"Run whisk init."}]}}}MANIFEST_INVALID
Status: 422 · Surface: api, git, cli
When: whisk.yaml does not parse, fails the schema for its version, or breaks a manifest rule. details.problems lists every problem with a path so all can be fixed in one pass. A deploy also fails with it when a static folder is not in the commit or holds no files (problem path /static/<i>/dir): static folders are read from the commit, so build output such as dist/, which is git-ignored, is not there.
Fix: Fix each problem listed; whisk doctor shows the same list locally with the schema description of the field. whisk schema prints the schema.
{"error":{"code":"MANIFEST_INVALID","message":"whisk.yaml has 2 problems.","fix":"Fix each listed problem and run whisk doctor. Problems: /functions/0: needs exactly one of cron or event; /webhooks/0/secret: STRIPE_WEBHOOK_SECRET is not listed under secrets.","docs":"https://skill.whisk.run/errors/MANIFEST_INVALID","details":{"problems":[{"path":"/functions/0","message":"needs exactly one of cron or event"},{"path":"/webhooks/0/secret","message":"STRIPE_WEBHOOK_SECRET is not listed under secrets"}]}}}MANIFESTUNKNOWNKEY
Status: 422 · Surface: api, git, cli
When: whisk.yaml contains a key the schema does not know. Usually a typo (function: for functions:) or a key from a later conventions version.
Fix: Remove or rename the key; the message suggests the nearest known key. Keys from a newer version need whisk: <version> and the platform to support it.
{"error":{"code":"MANIFEST_UNKNOWN_KEY","message":"whisk.yaml has an unknown key function at the top level.","fix":"Rename function to functions, or remove it. Known keys at this level are listed in whisk schema.","docs":"https://skill.whisk.run/errors/MANIFEST_UNKNOWN_KEY","details":{"path":"/function","suggestion":"functions"}}}CONVENTIONSVERSIONUNSUPPORTED
Status: 422 · Surface: api, git, cli
When: whisk: names a version this platform does not serve. Every published version is supported forever, so this means a version that does not exist yet or a malformed value.
Fix: Set whisk: 1, the current version, or update the CLI with whisk update if the message says the CLI is behind.
{"error":{"code":"CONVENTIONS_VERSION_UNSUPPORTED","message":"whisk.yaml declares conventions version 3; this platform serves versions 1.","fix":"Set whisk: 1 in whisk.yaml.","docs":"https://skill.whisk.run/errors/CONVENTIONS_VERSION_UNSUPPORTED","details":{"declared":3,"supported":[1]}}}SECRETINCOMMIT
Status: 422 · Surface: git, cli
When: a pushed commit contains a value shaped like a secret (an API key, a private key, a connection string with a password). The push is rejected before it is stored.
Fix: Remove the value from the file, declare a name for it under secrets in whisk.yaml, read it from the environment, amend or rewrite the commit so the value is not in history, and ask a human to set the value in the dashboard. Rotate the leaked value with the provider.
{"error":{"code":"SECRET_IN_COMMIT","message":"A value that looks like a Stripe secret key is in src/config.ts line 12.","fix":"Remove the value, declare STRIPE_SECRET_KEY under secrets in whisk.yaml, read it from the environment, rewrite the commit, and ask an owner to set it at https://whisk.run/o/acme/secrets. Rotate the key in Stripe.","docs":"https://skill.whisk.run/errors/SECRET_IN_COMMIT","details":{"file":"src/config.ts","line":12,"rule":"stripe-secret","commit":"a1b2c3d"}}}SECRETVALUENEEDS_HUMAN
Status: 403 · Surface: api, cli
When: an agent token tried to set a secret's value. Agents may declare names; only a human session may set values.
Fix: Tell the human the secret name and the URL where they set it. Do not ask them for the value and do not put it in a file.
{"error":{"code":"SECRET_VALUE_NEEDS_HUMAN","message":"Agent tokens cannot set secret values.","fix":"Ask an owner or admin to set XERO_CLIENT_SECRET at https://whisk.run/o/acme/secrets/XERO_CLIENT_SECRET. The deploy will restart automatically once it is set.","docs":"https://skill.whisk.run/errors/SECRET_VALUE_NEEDS_HUMAN","details":{"name":"XERO_CLIENT_SECRET","url":"https://whisk.run/o/acme/secrets/XERO_CLIENT_SECRET"}}}SECRET_SHREDDED
Status: 410 · Surface: api
When: a secret of an org that has been shredded was asked for. Its data key was destroyed, so nothing sealed under it can be opened by anyone.
Fix: Nothing can bring the value back. If the org is to run again, it is a new org with new values.
{"error":{"code":"SECRET_SHREDDED","message":"acme was shredded on 2026-11-07; its secrets cannot be read.","fix":"Nothing can bring the value back. If the org is to run again, it is a new org with new values.","docs":"https://skill.whisk.run/errors/SECRET_SHREDDED","details":{"org":"acme","shredded_at":"2026-11-07T00:00:00Z"}}}SECRET_UNSET
Status: 409 · Surface: api, deploy, cli
When: a deploy needs a declared secret that has no value yet. The deploy is live if the app starts without it, otherwise it is blocked; the CLI prints a NEEDS_HUMAN block either way. A build.secrets name with no value blocks the deploy before the build starts (details.build is true), because the build cannot run without it.
When another app of the business already has its own value under a missing name, details.elsewhere maps that name to those apps' slugs.
Fix: Relay the names and the URL to the human. Nothing else is needed; the platform restarts the app when the value arrives. For a name in details.elsewhere, first ask the human whether this app may use the same value as that app; if they agree, run whisk secrets share NAME --from <app> instead of asking for the value.
{"error":{"code":"SECRET_UNSET","message":"2 declared secrets have no value: XERO_CLIENT_SECRET, SLACK_WEBHOOK_URL.","fix":"XERO_CLIENT_SECRET is already set for crm. Ask the human whether this app may use the same value; if so, run whisk secrets share XERO_CLIENT_SECRET --from crm. Ask an owner or admin to set the rest at https://whisk.run/o/acme/secrets. The app restarts automatically when they are set.","docs":"https://skill.whisk.run/errors/SECRET_UNSET","details":{"names":["XERO_CLIENT_SECRET","SLACK_WEBHOOK_URL"],"elsewhere":{"XERO_CLIENT_SECRET":["crm"]},"url":"https://whisk.run/o/acme/secrets"}}}SECRETSHAREDEXISTS
Status: 409 · Surface: api, cli, dashboard
When: an app's secret was to be shared with every app of the business, but the business already has a shared secret of that name with its own value, which sharing would replace.
Fix: Keep both: the app reads its own value and the other apps the shared one. To have every app use one value, delete the one that should not win and share or set the other.
{"error":{"code":"SECRET_SHARED_EXISTS","message":"acme already has a shared XERO_CLIENT_SECRET with its own value.","fix":"Apps that name XERO_CLIENT_SECRET without their own value already read the shared one. To use crm's value everywhere instead, delete the shared one at https://whisk.run/o/acme/keys and share again.","docs":"https://skill.whisk.run/errors/SECRET_SHARED_EXISTS","details":{"name":"XERO_CLIENT_SECRET","app":"crm"}}}SECRETTOOSHORT
Status: 422 · Surface: api, cli
When: a value under 8 characters was set, from the dashboard or the API. Short values cannot be scrubbed from logs reliably, so the platform refuses them.
Fix: Use the real value; if the real value is genuinely short, put it in env instead because it is not being treated as a secret.
{"error":{"code":"SECRET_TOO_SHORT","message":"Values must be at least 8 characters so they can be scrubbed from logs.","fix":"Set the full value. If it is genuinely short and not sensitive, declare it under env in whisk.yaml instead.","docs":"https://skill.whisk.run/errors/SECRET_TOO_SHORT","details":{"name":"REGION_CODE","minimum":8}}}BUILD_FAILED
Status: - · Surface: deploy, cli
When: the image build exited non-zero. The last 200 lines of build output are in details.log and the CLI prints the last 40.
Fix: Read the log excerpt. Typical causes: a dependency install failed, a compile error, a Dockerfile step failed, a build-time secret is missing from build.secrets. Fix and redeploy.
{"error":{"code":"BUILD_FAILED","message":"The build failed at step 5 of 9 (npm ci).","fix":"Read the log excerpt, fix the cause, and run whisk deploy again. The previous deploy is still live.","docs":"https://skill.whisk.run/errors/BUILD_FAILED","details":{"build_id":"01J9","step":"npm ci","exit_code":1,"log":["npm ERR! code E404","npm ERR! 404 Not Found - GET https://registry.npmjs.org/left-padd"]}}}BUILD_TIMEOUT
Status: - · Surface: deploy, cli
When: the build ran past 20 minutes.
Fix: Make the build smaller: a lockfile so installs are cached, a multi-stage Dockerfile, no tests in the build. Check for a step waiting on input.
{"error":{"code":"BUILD_TIMEOUT","message":"The build was stopped after 20 minutes.","fix":"Add a lockfile so dependency installs cache, move tests out of the build, and check that no step waits for input. Redeploy.","docs":"https://skill.whisk.run/errors/BUILD_TIMEOUT","details":{"build_id":"01J9","limit_seconds":1200}}}IMAGEPULLFAILED
Status: - · Surface: deploy
When: the node could not pull the built image from the registry. A platform fault, not the app's: a deploy that meets it fails as PLATFORM_DEPLOY_FAILED with this code as details.cause_code.
Fix: Retry whisk deploy. If it fails twice, the platform has already paged the operator; check https://whisk.run/status.
{"error":{"code":"IMAGE_PULL_FAILED","message":"The node could not pull image sha256:9f3c… from the registry.","fix":"Run whisk deploy again. If it fails twice the operator has been paged; see https://whisk.run/status.","docs":"https://skill.whisk.run/errors/IMAGE_PULL_FAILED","details":{"digest":"sha256:9f3c","node":"node1"}}}HEALTHCHECKFAILED
Status: - · Surface: deploy, cli
When: the new container kept running but did not answer 200 on health.path within health.timeout seconds. The previous deploy stays live and the new container is stopped. details.log has the container's last 200 lines, details.path the path, details.timeout_seconds the limit and details.last_status the last HTTP status it answered (0 when nothing answered). A container that exited instead is CONTAINER_CRASHED.
Fix: Read the log. Typical causes: not listening on PORT (8080) on all interfaces, the health route missing or answering non-200, a crash at start, a slow start beyond the timeout. Run the app locally with whisk dev and fetch the health path.
{"error":{"code":"HEALTH_CHECK_FAILED","message":"GET /health did not answer 200 within 90 seconds; nothing answered.","fix":"Check the app listens on PORT (8080) on 0.0.0.0 and that GET /health returns 200. The log excerpt shows the last lines before the timeout. Redeploy after fixing.","docs":"https://skill.whisk.run/errors/HEALTH_CHECK_FAILED","details":{"path":"/health","timeout_seconds":90,"last_status":0,"log":["Listening on 127.0.0.1:3000"],"fault":"app"}}}CONTAINER_CRASHED
Status: - · Surface: deploy, cli
When: the new container exited during start, before it answered its health check (a deploy fails; details.log has its last lines and details.path the health path), or a live app's container exited and its three restarts did too (the app stops; whisk status and the app's problem carry the exit code and last lines). The exit code is known only for the second.
Fix: Read the log lines. A crash at start is usually a missing environment variable or secret, a missing file or a syntax error; run the app with whisk dev to see it. Exit code 137 is the memory limit; reduce memory use or ask for a larger plan. Other codes are the app's own exit.
{"error":{"code":"CONTAINER_CRASHED","message":"The app exited during start, before GET /health answered.","fix":"Read the log lines and fix the cause; run it with whisk dev to see it start. Exit code 137 would mean the memory limit was hit.","docs":"https://skill.whisk.run/errors/CONTAINER_CRASHED","details":{"path":"/health","log":["Error: DATABASE_URL is not set"],"fault":"app"}}}MIGRATE_FAILED
Status: - · Surface: deploy, cli
When: the migrate command exited non-zero, or ran longer than 600 seconds and was stopped (details.timed_out, details.timeout_seconds); the previous deploy is still live. The live app keeps writing while a migration runs, so the database is rolled back only when the migration changed its definitions (tables, columns, constraints, indexes, views, functions, types): it is then restored to the restore point taken before the migration, the message names the window whose writes are not in the live database, details.kept_copy names the replaced database (kept for 7 days and readable with whisk db query --database <kept_copy>), and details.writes_not_kept holds the window. A migration that changed no definitions (one in a transaction undoes itself) leaves the database as it is, with details.restored left as it was; data it changed outside a transaction stays changed. A restore point that did not reach the backup archive fails the deploy before the migration runs, with details.step snapshot.
Fix: Read the output in details.log. Fix the migration and redeploy. Write migrations to run in one transaction, so a failure leaves nothing to roll back. A migration that needs longer than 600 seconds is split into smaller ones, or its long backfill moves to a function. If details.kept_copy is set and users wrote during the window, read their rows from the kept copy with whisk db query --database and write them back.
{"error":{"code":"MIGRATE_FAILED","message":"The migrate command failed (exit 1); the migration changed none of the database's tables, columns, indexes or functions, so the database was left as it was and nothing the app's users wrote was undone.","fix":"Fix the migration using the output below and redeploy. The previous deploy is still live.","docs":"https://skill.whisk.run/errors/MIGRATE_FAILED","details":{"command":"node dist/migrate.js","exit_code":1,"restored":"left as it was","snapshot":"deploy_01J9","log":["error: relation \"notes\" already exists"]}}}PLATFORMDEPLOYFAILED
Status: - · Surface: deploy, cli
When: a deploy failed on Whisk's side, not in the app: the builder or the registry failed, no node was ready, a node could not prepare the database or cache, apply its network rules, pull the image, start the container, take a restore point or finish running the migration, or the edge could not be updated. details.fault is platform, details.cause_code names the part that failed (NODE_DOWN, IMAGE_PULL_FAILED, POLICY_APPLY_FAILED, DATABASE_FAILED, BUILD_FAILED for the builder, MIGRATE_FAILED for the node, and so on), details.step_code the deploy step when that differs, and details.cause the node's own words where it gave them. The operator is told the cause when it happens. The CLI exits 5, not 6.
Fix: Do not change the code. Run whisk deploy again; if it fails the same way, wait a few minutes and run it again.
{"error":{"code":"PLATFORM_DEPLOY_FAILED","message":"This deploy failed on Whisk's side, not in your code: the migration could not start on node node1: the node's network rules would not load (network policy could not be applied). It did not run, so the database is as it was and the previous deploy is still live.","fix":"Do not change your code for this. Run whisk deploy again; if it fails the same way, wait a few minutes and run it again, since Whisk's operator has already been told the cause.","docs":"https://skill.whisk.run/errors/PLATFORM_DEPLOY_FAILED","details":{"fault":"platform","cause_code":"POLICY_APPLY_FAILED","step_code":"POLICY_APPLY_FAILED","step":"migrate","node":"node1","cause":"network policy could not be applied"}}}DEPLOY_FAILED
Status: - · Surface: cli
When: a deploy whisk deploy was following ended failed and the platform recorded no error code for it, so the CLI cannot name a more specific one. details names deploy_id and status, and details.log holds the last lines of the build or app log when there are any. The CLI exits 6.
Fix: Run whisk deploys info <id> for the phases and whisk logs for the app's output, fix the cause, and run whisk deploy again. If nothing in either points at the app, report it with whisk feedback --kind bug --code DEPLOY_FAILED.
{"error":{"code":"DEPLOY_FAILED","message":"Deploy 01J9 ended with status failed and no error was recorded.","fix":"Run whisk deploys info 01J9 for the phases, fix the cause, and run whisk deploy again.","docs":"https://skill.whisk.run/errors/DEPLOY_FAILED","details":{"deploy_id":"01J9","status":"failed"}}}DEPLOY_CANCELLED
Status: - · Surface: deploy, cli
When: someone cancelled the deploy, or a newer push superseded it.
Fix: Nothing, if a newer deploy replaced it. Otherwise run whisk deploy again.
{"error":{"code":"DEPLOY_CANCELLED","message":"Deploy 01J9 was cancelled by ana@acme.example.","fix":"Run whisk deploy again if the cancellation was not intended.","docs":"https://skill.whisk.run/errors/DEPLOY_CANCELLED","details":{"deploy_id":"01J9","cancelled_by":"ana@acme.example"}}}DEPLOYINPROGRESS
Status: - · Surface: cli
When: whisk deploy would wait on a deploy in the same environment that has sat queued or building for 25 minutes or more, longer than any build may run (BUILD_TIMEOUT): the remote already had the commit and that commit's deploy is the one stuck, or git push lost its connection to the platform while such a deploy was there. details names deploy_id, status, environment, since (when it entered that status), waited_seconds, and commit_sha; after a push, git's last lines in details.git. The platform itself ends a deploy nothing is working on within a few minutes, so this is rare. The CLI exits 1.
Fix: Wait for it (whisk deploys info <id>), or cancel it with whisk deploys cancel <id> and run whisk deploy again, which builds the commit again when its build never finished.
{"error":{"code":"DEPLOY_IN_PROGRESS","message":"Deploy 01J9 has been building for 47 minutes without finishing.","fix":"Wait for it with whisk deploys info 01J9, or cancel it with whisk deploys cancel 01J9 and run whisk deploy again.","docs":"https://skill.whisk.run/errors/DEPLOY_IN_PROGRESS","details":{"deploy_id":"01J9","status":"building","environment":"production","since":"2026-10-06T09:13:00Z","waited_seconds":2820,"commit_sha":"3f9c2a1b7d0e4c55a1b2c3d4e5f60718293a4b5c"}}}NEEDS_DEPLOY
Status: 503 · Surface: edge
When: a sleeping app was asked to wake but its container no longer exists on the node, for example after a node rebuild. The platform redeploys the last live build automatically.
Fix: Wait a minute and retry. If it persists, run whisk deploy.
{"error":{"code":"NEEDS_DEPLOY","message":"job-tracker is being redeployed and will answer shortly.","fix":"Retry in a minute. If the app still does not answer, run whisk deploy.","docs":"https://skill.whisk.run/errors/NEEDS_DEPLOY","details":{"app":"job-tracker"}}}POLICYAPPLYFAILED
Status: - · Surface: deploy
When: the node could not apply the network policy for the container or the migration, so it was not started. A platform fault; the database is untouched and the previous deploy stays live. A deploy that meets it fails as PLATFORM_DEPLOY_FAILED with this code as details.cause_code. details carries step, node, cause (nft's own words) and fault: platform, and the operator is paged.
Fix: Retry whisk deploy. The operator has been paged if it fails twice.
{"error":{"code":"POLICY_APPLY_FAILED","message":"The node could not apply network policy for the container and refused to start it.","fix":"Run whisk deploy again. If it fails twice the operator has been paged.","docs":"https://skill.whisk.run/errors/POLICY_APPLY_FAILED","details":{"node":"node1"}}}CSRF_REJECTED
Status: 403 · Surface: edge, auth, api
When: a non-GET request with cookies (the platform session or the app's own) came from anywhere but the app's own pages: Sec-Fetch-Site was cross-site or same-site (another app on the apps domain), or the Origin host differed from the request host. On the sign-in host, any POST whose Origin is not that host. On the API, a request that changes state with the Whisk session cookie whose Origin is not the dashboard, or, with no Origin, whose Sec-Fetch-Site is cross-site or same-site.
Fix: Make the request from the app's own pages (on the API, from the dashboard). For a deliberate cross-site embed, list the route under routes.csrf_off. API clients should send a bearer token instead of a cookie.
{"error":{"code":"CSRF_REJECTED","message":"A POST to /notes with cookies came from another site or another app.","fix":"Send the request from the app's own pages, or list /notes under routes.csrf_off in whisk.yaml if a cross-site POST is intended.","docs":"https://skill.whisk.run/errors/CSRF_REJECTED","details":{"route":"/notes","sec_fetch_site":"cross-site"}}}REAUTH_REQUIRED
Status: 403 · Surface: auth
When: on the sign-in host, a signed-in person asked to add or remove a passkey, or to set, change or remove their password, more than ten minutes after they last proved who they are: the sign-in that made their session, or a confirmation since. Nothing was changed. The pages show the confirm page instead; the passkey ceremony's script gets this answer.
Fix: Open /passkeys/confirm on the sign-in host and confirm it's you with a passkey, your password or an emailed code (or your company's sign-in, when your company signs you in), then make the change within ten minutes.
{"error":{"code":"REAUTH_REQUIRED","message":"Confirm it's you before you add a passkey.","fix":"Open https://auth.whisk.run/passkeys/confirm, confirm with a passkey, your password or an emailed code, then try again within ten minutes.","docs":"https://skill.whisk.run/errors/REAUTH_REQUIRED"}}PASSKEY_REQUIRED
Status: 403 · Surface: api, dashboard, edge
When: a member of a business acted in it through a session that began with an emailed code or a password, and an owner of the business turned on "Everyone signs in with a passkey or company sign-in". Only that business refuses the session: the person stays signed in and acts in their other businesses as before. An app of the business answers its private routes with this too, and serves its public routes as to anyone. An owner turning the setting on from such a session gets it as well, so nobody shuts themselves out.
Fix: Sign out, then sign in on the sign-in host with your passkey (or your company's sign-in). If you have no passkey yet, add one at /passkeys on the sign-in host first, then sign in with it.
{"error":{"code":"PASSKEY_REQUIRED","message":"Acme asks everyone to sign in with a passkey or company sign-in, and this session began with an emailed code or a password.","fix":"Sign out, then sign in at https://auth.whisk.run/session/login with your passkey. No passkey yet? Add one at https://auth.whisk.run/passkeys first, then sign in with it.","docs":"https://skill.whisk.run/errors/PASSKEY_REQUIRED","details":{"method":"code","org":"acme"}}}SSO_REQUIRED
Status: 403 · Surface: api, dashboard, edge
When: a member of a business that requires company sign-in (single sign-on enabled and its domain verified) acted in it through a session its own identity provider did not make: an emailed code, a password, a passkey, or another business's provider. It applies to every member, whatever the domain of their email. Only that business refuses the session; an app of the business answers its private routes with this too.
Fix: Sign out, then sign in on the sign-in host with your email at the business's domain, which sends you to its provider. If you are in the business under another email, ask an owner to invite your address at the business's domain.
{"error":{"code":"SSO_REQUIRED","message":"Acme signs its people in through its own provider, and this session did not come from it.","fix":"Sign out, then sign in at https://auth.whisk.run/session/login with your @acme.com email. If you are in Acme under another email, ask an owner to invite your @acme.com address.","docs":"https://skill.whisk.run/errors/SSO_REQUIRED","details":{"domain":"acme.com","method":"code","org":"acme"}}}PATH_AMBIGUOUS
Status: 400 · Surface: edge, auth
When: the request path has a . or .. segment, counted after percent-decoding and splitting on / and \ (so %2e%2e, ..%2f and ..\ count), or does not percent-decode. The edge classifies routes on the path as sent, and an app that resolves dot segments could serve a different route than the one the edge classified, so it refuses the path instead of guessing (CADDY.md §5.1 step 1). Browsers resolve dot segments before sending, so pages never see this.
Fix: Send the path already resolved, as a browser does: /a/b/../c is /a/c.
{"error":{"code":"PATH_AMBIGUOUS","message":"The request path has a . or .. segment, so the app could serve a different route than the one it names.","fix":"Send the path already resolved, as a browser does: /a/b/../c is /a/c.","docs":"https://skill.whisk.run/errors/PATH_AMBIGUOUS","details":{}}}CDNORIGINREQUIRED
Status: 403 · Surface: edge
When: a request reached the origin of a hostname that a CDN fronts, and it neither carried the shared X-Whisk-Edge-Key nor came from the CDN's published ranges (CADDY.md §3).
Fix: Send the request to the hostname's CDN address rather than to the origin. If the CDN itself is refused, its configured origin header or the platform's ranges are stale; an operator fixes either.
{"error":{"code":"CDN_ORIGIN_REQUIRED","message":"This hostname accepts requests only through its CDN.","fix":"Send the request to the hostname's CDN address, not to the origin; a CDN must present X-Whisk-Edge-Key or come from its published ranges.","docs":"https://skill.whisk.run/errors/CDN_ORIGIN_REQUIRED","details":{"hostname":"app.acme.com"}}}CHALLENGE_REQUIRED
Status: 403 · Surface: edge
When: a POST to a route in routes.challenge carried no solved Altcha payload, or an invalid one. The body contains a fresh challenge.
Fix: Solve details.challenge with the Altcha widget or library and resend with the solution in the altcha form field or the X-Altcha header.
{"error":{"code":"CHALLENGE_REQUIRED","message":"POST /contact needs a solved challenge.","fix":"Solve the challenge in details with the Altcha widget and resend with the payload in the altcha form field or the X-Altcha header.","docs":"https://skill.whisk.run/errors/CHALLENGE_REQUIRED","details":{"route":"/contact","challenge":{"algorithm":"SHA-256","challenge":"…","salt":"…","signature":"…","maxnumber":100000}}}}RATE_LIMITED
Status: 429 · Surface: edge, api, auth
When: a client exceeded a rate limit: per IP for a visitor who is not signed in, per user for one who is, per app, or per token on the API; or sent more feedback in an hour than POST /v1/feedback takes (details.scope is feedback); or sent more than ten browser error reports in a minute from one network address to POST /v1/client-errors (details.scope is client_errors); or started more than ten device logins (POST /v1/device/code) or polled more than sixty times (POST /v1/device/token) in a minute from one network address. On the sign-in host, more than five emailed codes for one address or thirty from one network address in an hour, or ten wrong passwords for one address or fifty from one network address. Retry-After says when to try again.
Fix: Wait Retry-After seconds. If a legitimate client hits the limit, the plan's limits are in the message; ask an owner about a larger plan.
{"error":{"code":"RATE_LIMITED","message":"More than 120 requests per minute from this address.","fix":"Wait 23 seconds and retry. Limits per plan are listed at https://skill.whisk.run/limits.","docs":"https://skill.whisk.run/errors/RATE_LIMITED","details":{"scope":"anonymous_ip","limit":120,"window_seconds":60,"retry_after":23}}}BODYTOOLARGE
Status: 413 · Surface: edge, api, hooks
When: a request body exceeded the limit: 8 MB on app routes (32 MB on Business), 5 MB on the webhook ingress, 1 MB on the API.
Fix: For file uploads, request a signed upload URL and send the file directly to storage. For anything else, send less.
{"error":{"code":"BODY_TOO_LARGE","message":"The request body is 12.4 MB; the limit on this route is 8 MB.","fix":"For files, POST /v1/orgs/acme/apps/job-tracker/uploads to get a signed URL and upload directly to storage.","docs":"https://skill.whisk.run/errors/BODY_TOO_LARGE","details":{"limit_bytes":8388608,"received_bytes":13002342}}}WEBHOOK_UNVERIFIED
Status: 401 · Surface: hooks
When: a delivery to hooks.whisk.run failed signature verification, or its timestamp was outside the preset's tolerance. The event is stored with verified: false, visible in the dashboard, and never delivered to the app.
Fix: Check that the secret set for the source matches the provider's signing secret, and that the preset matches the provider. Replay from the dashboard is not possible for unverified events; ask the provider to resend after fixing the secret.
{"error":{"code":"WEBHOOK_UNVERIFIED","message":"The signature on this delivery to source stripe did not verify.","fix":"Confirm STRIPE_WEBHOOK_SECRET matches the signing secret shown in the Stripe dashboard for this endpoint and that the source uses the stripe preset. Then resend the event from Stripe.","docs":"https://skill.whisk.run/errors/WEBHOOK_UNVERIFIED","details":{"source":"stripe","reason":"signature_mismatch","event_id":"01J9"}}}WEBHOOKSOURCEUNKNOWN
Status: 404 · Surface: hooks
When: the URL names an org, app or source that does not exist, or a token preset URL is missing its token.
Fix: Copy the URL printed by whisk webhooks add or shown in the dashboard exactly.
{"error":{"code":"WEBHOOK_SOURCE_UNKNOWN","message":"No webhook source named stripe-live exists on app job-tracker.","fix":"Use the exact URL from whisk webhooks list, or declare the source under webhooks in whisk.yaml and deploy.","docs":"https://skill.whisk.run/errors/WEBHOOK_SOURCE_UNKNOWN","details":{"org":"acme","app":"job-tracker","source":"stripe-live"}}}WEBHOOK_DEAD
Status: - · Surface: run, cli
When: a verified event could not be delivered to the handler after every retry (1m, 5m, 30m, 2h, 12h). It is dead-lettered and can be replayed.
Fix: Fix the handler so it answers 2xx within 30 seconds, deploy, then replay with whisk webhooks replay <source> <event-id>.
{"error":{"code":"WEBHOOK_DEAD","message":"Event 01J9 from source stripe was not delivered after 5 attempts; the last response was 500.","fix":"Fix the handler at /hooks/stripe so it answers 2xx within 30 seconds, deploy, then run whisk webhooks replay stripe 01J9.","docs":"https://skill.whisk.run/errors/WEBHOOK_DEAD","details":{"source":"stripe","event_id":"01J9","attempts":5,"last_status":500}}}DELIVERY_UNVERIFIED
Status: 401 · Surface: container
When: a request to a webhook handler did not carry a valid X-Whisk-Delivery-Signature under the app's WHISK_DELIVERY_KEY, or carried X-Whisk-Service-App, so it is not a delivery from the platform. The app's own handler answers this, through the templates' delivery helper; the platform never sends an unsigned delivery.
Fix: If a genuine delivery is refused, the handler is checking the wrong bytes: verify v1=hex(HMAC-SHA256(key, id + "\n" + received_at + "\n" + raw_body)) against fixtures/deliveries/. Anything else is another caller reaching the handler and the refusal is correct.
{"error":{"code":"DELIVERY_UNVERIFIED","message":"This request is not a delivery from the platform.","fix":"Webhook handlers accept only signed platform deliveries; call the app through its public routes instead.","docs":"https://skill.whisk.run/errors/DELIVERY_UNVERIFIED","details":{"handler":"/hooks/stripe"}}}INVOKE_FOREIGN
Status: 403 · Surface: run
When: a function called step.invoke on a function of another app. One workflow engine serves every app, so the edge refuses the engine's call into the invoked app unless the invocation came from that same app. The invoking step fails with this error and the invoked function never runs.
Fix: Invoke only your own app's functions. To use another app, call its service route with WHISK_SERVICE_TOKEN (app-to-app calls).
{"error":{"code":"INVOKE_FOREIGN","message":"Another app's function tried to invoke a function of this app.","fix":"A function can invoke only functions of its own app. To use another app, call its service route with your service token.","docs":"https://skill.whisk.run/errors/INVOKE_FOREIGN"}}RUN_PARKED
Status: 409 · Surface: api, run, cli
When: a function run failed after its retries, or a run guard stopped it (RUN_STEP_LIMIT, RUN_CONCURRENCY_LIMIT). The run is parked with its error and the owner is notified; details.step names the step that failed. A function a guard parked refuses the events that would start it with this code, from the API and from the SDK's own event calls alike, until the next deploy to go live declares it again.
Fix: Read the step error with whisk runs show <id>, fix the code, deploy, and replay with whisk runs replay <id>. For a parked function, fix it so its runs stay within the limit and deploy: the deploy lifts the parking when it goes live.
{"error":{"code":"RUN_PARKED","message":"Run 01J9 of nightly-margin failed at step compute-margins after 3 retries.","fix":"Run whisk runs show 01J9 to see the step error, fix it, deploy, then whisk runs replay 01J9.","docs":"https://skill.whisk.run/errors/RUN_PARKED","details":{"run_id":"01J9","function":"nightly-margin","step":"compute-margins","attempts":4,"error":"TypeError: cannot read properties of undefined"}}}RUNSTEPLIMIT
Status: - · Surface: run
When: a single run executed more than 1,000 steps, which almost always means a loop with no exit. The run is parked.
Fix: Bound the loop, or split the work across events so each run stays small.
{"error":{"code":"RUN_STEP_LIMIT","message":"Run 01J9 of sync-contacts executed 1,000 steps and was stopped.","fix":"Bound the loop in sync-contacts or emit one event per page of work so each run stays under 1,000 steps.","docs":"https://skill.whisk.run/errors/RUN_STEP_LIMIT","details":{"run_id":"01J9","function":"sync-contacts","limit":1000}}}RUNSTEPFAILED
Status: - · Surface: run
When: a step of a workflow run threw. The run's page carries it on the step, with the step's input and output beside it; the run's own error is its first failed step's.
Fix: Read the step's error and the app's logs, fix the code, push, and replay the run.
{"error":{"code":"RUN_STEP_FAILED","message":"Step charge-card failed: Error: card declined.","fix":"Read the step's error and the app's logs, fix the code, push, and replay the run.","docs":"https://skill.whisk.run/errors/RUN_STEP_FAILED","details":{"step":"charge-card","stack":"Error: card declined\n at charge (/app/dist/steps.js:12:9)"}}}RUN_FAILED
Status: - · Surface: run
When: a workflow run failed with an error the app's code threw, in the function body or in a step, and no step of the run names it (a failed step is RUN_STEP_FAILED). The message is the error's name and message, details.stack its stack, and details.fault is app.
Fix: Read the error and the app's logs, fix the code, push, and replay the run.
{"error":{"code":"RUN_FAILED","message":"The function failed: TypeError: Cannot read properties of undefined (reading 'id').","fix":"Read the error and the app's logs, fix the code, push, and replay the run.","docs":"https://skill.whisk.run/errors/RUN_FAILED","details":{"fault":"app","stack":"TypeError: Cannot read properties of undefined (reading 'id')\n at sync (/app/dist/sync.js:14:22)"}}}RUNAPPUNREACHABLE
Status: - · Surface: run
When: Whisk's edge answered for the app while a run was in progress because the app no longer did: its process exited or crashed, or a deploy replaced its container mid-run. The engine records the edge's 502 with no step output. A memory kill is APP_OUT_OF_MEMORY instead, and an edge that could not wake the app is PLATFORM_RUN_INTERRUPTED. details.fault is app.
Fix: Read the app's logs around the run's end for an exit or a crash, fix it, and replay the run.
{"error":{"code":"RUN_APP_UNREACHABLE","message":"The app stopped answering at 06:33:31 UTC while this run was in progress: its process exited or crashed, or a deploy replaced its container. Your server returned HTTP 502 before the SDK responded.","fix":"Read the app's logs around that time (whisk logs --since) for an exit or a crash, fix it, and replay the run. A memory kill would say APP_OUT_OF_MEMORY instead.","docs":"https://skill.whisk.run/errors/RUN_APP_UNREACHABLE","details":{"fault":"app","engine_error":"Your server returned HTTP 502 before the SDK responded."}}}PLATFORMRUNINTERRUPTED
Status: - · Surface: run
When: Whisk cut a workflow run off: the engine lost its connection to the app, which it reaches only through Whisk's edge (in practice the edge restarting during a Whisk release), or the edge could not wake the app (details.cause_code, such as WAKE_TIMEOUT). Nothing in the app caused it, and details.fault is platform. Whisk runs the interrupted run again once, from the same trigger, within a minute and while the failure is under two hours old; the new run's id is details.rerun_run_id and the run's rerun_run_id, and the new run carries rerun_of. A run that is itself a re-run is not run again, and nor is a scheduled run that a later run of its function has already followed. Only a run that had finished no step is re-run, so nothing that completed runs twice; a run with a finished step is left to replay. A function with retries has the lost step retried inside the same run by the engine instead.
Fix: Do not change the code. If details.rerun_run_id is set, follow that run instead; otherwise replay the run once its finished steps can safely run again.
{"error":{"code":"PLATFORM_RUN_INTERRUPTED","message":"Whisk lost its connection to the app at 06:33:31 UTC while this run was in progress, most likely because Whisk's edge restarted during a Whisk release. Nothing in your code caused it.","fix":"Do not change your code for this. When no step of the run had finished, Whisk runs it again once by itself and details.rerun_run_id names the new run. Otherwise replay it with whisk runs replay once you know its finished steps can safely run again.","docs":"https://skill.whisk.run/errors/PLATFORM_RUN_INTERRUPTED","details":{"fault":"platform","engine_error":"Unable to reach SDK URL","rerun_run_id":"01M3P163Z5V68KBQ34J4FD51EA"}}}RUNCONCURRENCYLIMIT
Status: - · Surface: run
When: more runs of one function were in flight at once than the plan allows (the plan's concurrent_runs, 20 by default), which almost always means an event sent in a loop. The function is parked; the runs already started finish.
Fix: Send one event carrying a batch instead of one per item, or let the runs drain and push again; a push declares the function again and lifts the parking.
{"error":{"code":"RUN_CONCURRENCY_LIMIT","message":"sync-contacts had 41 runs in flight at once, over the limit of 20, and was parked.","fix":"Send one event carrying a batch instead of one per item, then push to lift the parking.","docs":"https://skill.whisk.run/errors/RUN_CONCURRENCY_LIMIT","details":{"function":"sync-contacts","limit":20,"observed":41}}}EVENTRATELIMITED
Status: 429 · Surface: api
When: the org sent more events per minute than the plan allows. The event was not accepted.
Fix: Wait Retry-After and resend, or batch: one event carrying a list is one event.
{"error":{"code":"EVENT_RATE_LIMITED","message":"acme sent more than 600 events in a minute.","fix":"Wait 17 seconds and resend, or send one event carrying a batch instead of one per item.","docs":"https://skill.whisk.run/errors/EVENT_RATE_LIMITED","details":{"limit":600,"window_seconds":60,"retry_after":17}}}GRAPH_DRIFT
Status: - · Surface: run, cli
When: observed runs of a function executed a step that is not in its declared graph, or a declared step has never run. Reported by whisk functions graph and the dashboard; runs are not affected.
Fix: Update the graph file to match the code, or the code to match the graph. whisk doctor flags step names that appear in one but not the other before you deploy.
{"error":{"code":"GRAPH_DRIFT","message":"po-approval ran step notify-buyer, which is not in workflows/po-approval.graph.yaml.","fix":"Add notify-buyer to workflows/po-approval.graph.yaml where it belongs, or rename the step in code to a declared id.","docs":"https://skill.whisk.run/errors/GRAPH_DRIFT","details":{"function":"po-approval","unexpected":["notify-buyer"],"never_ran":[]}}}FUNCTIONSNOTREGISTERED
Status: - · Surface: deploy (a warning), cli
When: a deploy went live but its functions could not be registered with the workflow engine, so their crons and events will not start runs. It is a warning on the deploy (warnings), not a failure: the app serves. details.functions names each function whisk.yaml declares that the app's code does not serve (the manifest's name and the code's function id differ), or details.cause gives the engine's error when the app's function route did not answer (the SDK route not mounted, or the app erroring on it).
Fix: Make each name in whisk.yaml match the id the code gives the function (createFunction or its equivalent), and check the app serves the SDK's route; whisk doctor compares the names. Then run whisk deploy again, which registers the functions afresh.
{"error":{"code":"FUNCTIONS_NOT_REGISTERED","message":"whisk.yaml declares nightly-report, which the app's code does not serve under that name, so it never runs.","fix":"Make each name under functions in whisk.yaml the id the code gives the function (createFunction's id, fn_id, the ID option), then deploy again.","details":{"functions":["nightly-report"]},"docs":"https://skill.whisk.run/errors/FUNCTIONS_NOT_REGISTERED"}}FUNCTIONNOTLIVE
Status: 409 · Surface: api, cli
When: whisk cron run (or POST /orgs/:org/apps/:app/cron/:function/run) named a cron function that cannot run yet. details.reason says why: not_deployed (no deploy of the app has gone live, so nothing serves the function), app_stopped (the app's container stopped and the platform marked it broken), not_registered (the workflow engine does not yet hold the function with the trigger whisk cron run sends, which it gains moments after traffic switches or when the platform re-registers the app), or too_many_triggers (the function already has the engine's most triggers, 10, so there is no room for that one). No run was started.
Fix: For not_deployed, push and wait for whisk deploy to report it live. For app_stopped, read why with whisk logs, push a fix or run whisk deploy. For not_registered, wait a minute and run it again; if it persists, push again, since a deploy registers the function afresh. For too_many_triggers, remove one trigger from the function and push. Then run whisk cron run <function> again.
{"error":{"code":"FUNCTION_NOT_LIVE","message":"nightly-margin cannot run yet: no deploy of crm has gone live.","fix":"Push with whisk deploy and wait for it to go live, then run whisk cron run nightly-margin again.","docs":"https://skill.whisk.run/errors/FUNCTION_NOT_LIVE","details":{"app":"crm","function":"nightly-margin","reason":"not_deployed"}}}APPNOTLIVE
Status: 409 · Surface: api, cli
When: something that reads the app's live production deploy was asked of an app that has none, such as whisk scan --now (or POST /orgs/:org/apps/:app/packages/scan) before the app's first production deploy went live. Nothing was started.
Fix: Run whisk deploy and wait for it to report live, then ask again.
{"error":{"code":"APP_NOT_LIVE","message":"crm has no live production deploy to check.","fix":"Run whisk deploy and wait for it to report live, then ask again.","docs":"https://skill.whisk.run/errors/APP_NOT_LIVE","details":{"app":"crm"}}}DOMAIN_UNVERIFIED
Status: 409 · Surface: api, cli
When: a custom domain's DNS records are not in place yet. The TXT proves ownership; the CNAME routes traffic. A bare domain such as acme.example, which cannot have a CNAME, uses A and AAAA records to details.addresses instead. details.missing lists TXT, CNAME (meaning the name does not point at the app either way), or both.
Fix: Create the records in details.records at the DNS provider, wait for propagation, and run whisk domains verify <hostname>.
{"error":{"code":"DOMAIN_UNVERIFIED","message":"jobs.acme.example is not verified: the TXT record was not found yet.","fix":"Add TXT _whisk-verify.jobs.acme.example = whisk-verify-9f3c and CNAME jobs.acme.example -> job-tracker.acme.whisk.page (for a bare domain like example.com, which cannot have a CNAME, A or AAAA records to 95.217.38.236 instead), wait for DNS to update, then run whisk domains verify jobs.acme.example.","docs":"https://skill.whisk.run/errors/DOMAIN_UNVERIFIED","details":{"hostname":"jobs.acme.example","records":[{"type":"TXT","name":"_whisk-verify.jobs.acme.example","value":"whisk-verify-9f3c"},{"type":"CNAME","name":"jobs.acme.example","value":"job-tracker.acme.whisk.page"}],"addresses":["95.217.38.236"],"missing":["TXT"]}}}SSODOMAINUNVERIFIED
Status: 409 · Surface: api
When: SSO was enabled for an email domain whose TXT record is not in place yet. Company sign-in takes over every email at the domain, so the org must show it holds the domain first.
Fix: Create the record in details.records at the DNS provider, wait for propagation, and save the SSO settings again.
{"error":{"code":"SSO_DOMAIN_UNVERIFIED","message":"acme.example is not verified for company sign-in: the TXT record was not found.","fix":"Add TXT _whisk-sso.acme.example = whisk-sso-4b1e9c, then save the SSO settings again at https://whisk.run/o/acme/settings.","docs":"https://skill.whisk.run/errors/SSO_DOMAIN_UNVERIFIED","details":{"domain":"acme.example","records":[{"type":"TXT","name":"_whisk-sso.acme.example","value":"whisk-sso-4b1e9c"}]}}}DOMAIN_TAKEN
Status: 409 · Surface: api, cli
When: the hostname is already attached to another app, possibly in another org, or is an app's address on another business's own domain, or a business domain is already another business's, or an email domain is already used for company sign-in by another org (details.kind is sso).
Fix: Remove it from the other app first, or use a different hostname. If another org holds your domain, contact support with proof of ownership.
{"error":{"code":"DOMAIN_TAKEN","message":"jobs.acme.example is already attached to another app.","fix":"Run whisk domains remove jobs.acme.example on the app that holds it, or choose another hostname. Contact support@whisk.run if you own the domain and do not control that app.","docs":"https://skill.whisk.run/errors/DOMAIN_TAKEN","details":{"hostname":"jobs.acme.example"}}}ORGDOMAINEXISTS
Status: 409 · Surface: api, cli
When: the business already has its own domain (every app at <app>.<domain>); a business has one. details.domain is the one it has.
Fix: Keep using it, or have an owner or admin remove it in the dashboard (Settings, Your domain) and add the new one.
{"error":{"code":"ORG_DOMAIN_EXISTS","message":"This business already uses acme.example.","fix":"Remove it first (owner, in the dashboard), then add the new one.","docs":"https://skill.whisk.run/errors/ORG_DOMAIN_EXISTS","details":{"domain":"acme.example"}}}EMAILDOMAINUNVERIFIED
Status: 403 · Surface: api
When: from in an email send names a domain the org has not verified. A send without from goes from Whisk's shared address and never answers this.
Fix: Leave from out to send from Whisk's address, send from a verified domain, or ask an owner to add and verify the domain in the dashboard (the DNS records are shown there).
{"error":{"code":"EMAIL_DOMAIN_UNVERIFIED","message":"acme.example is not a verified sending domain for acme.","fix":"Leave from out to send from Whisk's address, send from a verified domain, or ask an owner to verify acme.example at https://whisk.run/o/acme/settings.","docs":"https://skill.whisk.run/errors/EMAIL_DOMAIN_UNVERIFIED","details":{"from":"noreply@acme.example","verified":["mail.acme.example"]}}}EMAIL_PAUSED
Status: 403 · Surface: api
When: the org's sending is paused because its bounce or complaint rate crossed the threshold. Owners were notified.
Fix: Stop sending to addresses that bounced, then ask an owner to request a resume from the email settings page.
{"error":{"code":"EMAIL_PAUSED","message":"Sending for acme is paused: the bounce rate over the last 24 hours was 7.2%.","fix":"Remove bouncing addresses from your lists, then ask an owner to resume sending at https://whisk.run/o/acme/settings.","docs":"https://skill.whisk.run/errors/EMAIL_PAUSED","details":{"domain":"mail.acme.example"}}}EMAILLINKBLOCKED
Status: 403 · Surface: api
When: the message links to an address a malware or phishing list names, or to an app Whisk is reviewing. The message is not sent, and the business is held for review (ORG_UNDER_REVIEW).
Fix: Remove the link. If the address is yours and the list is wrong, write to support@whisk.run.
{"error":{"code":"EMAIL_LINK_BLOCKED","message":"The message links to bad.example, which a phishing list names.","fix":"Remove the link. If the address is yours and the list is wrong, write to support@whisk.run.","docs":"https://skill.whisk.run/errors/EMAIL_LINK_BLOCKED","details":{"host":"bad.example"}}}EMAILRATELIMITED
Status: 429 · Surface: api
When: the org has sent its daily allowance (on a plan that does not bill past it) or its monthly allowance, or 100 recipients in a minute. details.period is day or month for an allowance; Retry-After gives the seconds until the window resets.
Fix: Wait, or ask an owner about a larger allowance. Transactional email should rarely hit this; a burst usually means a loop.
{"error":{"code":"EMAIL_RATE_LIMITED","message":"acme has sent 1,000 emails today, the plan's daily limit.","fix":"Wait 5h12m for the window to reset, and check for a loop if the volume was unexpected.","docs":"https://skill.whisk.run/errors/EMAIL_RATE_LIMITED","details":{"limit":1000,"sent":1000,"retry_after":18720}}}EMAILSENDERINVALID
Status: 400 · Surface: api
When: the operator set the platform's mail sender (PUT /v1/operator/email/resend) with a key that cannot be used: it is empty, holds spaces or line breaks, does not start with re_, Resend does not accept it, or Resend will not send from any of the platform's domains with it. details.reason is format, refused or domain; for domain, details.tried lists the domains tried.
Fix: Create a key with full access at https://resend.com/api-keys and paste it whole. For domain, verify one of the domains in details.tried at https://resend.com/domains first.
{"error":{"code":"EMAIL_SENDER_INVALID","message":"Resend will not send from whisk.run or whisk.page.","fix":"Verify one of those domains at https://resend.com/domains, then paste the key again.","docs":"https://skill.whisk.run/errors/EMAIL_SENDER_INVALID","details":{"reason":"domain","tried":["whisk.run","whisk.page"]}}}STRIPEKEYINVALID
Status: 400 · Surface: api, dashboard
When: the operator set the platform's Stripe account (PUT /v1/operator/payments/stripe) with a key that cannot be used: it is empty, holds spaces or line breaks, is not a secret key (sk_live_, sk_test_, or a restricted rk_ key), Stripe does not accept it, or it may not make the webhook endpoint billing needs. details.reason is format, refused or permissions.
Fix: Copy the secret key from https://dashboard.stripe.com/apikeys and paste it whole. For permissions, paste the account's standard secret key rather than a restricted key.
{"error":{"code":"STRIPE_KEY_INVALID","message":"That is the publishable key. Paste the secret key, which starts with sk_live_.","fix":"Copy the secret key from https://dashboard.stripe.com/apikeys and paste it whole.","docs":"https://skill.whisk.run/errors/STRIPE_KEY_INVALID","details":{"reason":"format"}}}STRIPEACCOUNTIN_USE
Status: 409 · Surface: api, dashboard
When: the operator pasted a Stripe key for another account, or the other mode (test or live), than the one businesses already pay through. Their customers, cards and subscriptions live in that account, so switching would stop their billing.
Fix: Paste a key for the same account and mode as details.account_id and details.mode.
{"error":{"code":"STRIPE_ACCOUNT_IN_USE","message":"3 businesses already pay through the Stripe account in use (acct_1Abc, live mode).","fix":"Paste a key for that same account and mode; moving businesses to another Stripe account is not something Whisk does.","docs":"https://skill.whisk.run/errors/STRIPE_ACCOUNT_IN_USE","details":{"account_id":"acct_1Abc","customers":3,"mode":"live"}}}CDN_UNAVAILABLE
Status: 503 · Surface: api, cli, dashboard
When: an app's CDN was turned on (PUT /orgs/:org/apps/:app/cdn {"on": true}) on a platform whose operator has not set a CDN provider yet (CONTROL-PLANE.md §6.29).
Fix: Leave the CDN off for now; the app keeps being served directly. Try again once the platform's operator has set the CDN up.
{"error":{"code":"CDN_UNAVAILABLE","message":"The CDN is not set up on this platform yet.","fix":"Leave the CDN off for now; the app keeps being served directly. Try again once the platform's operator has set the CDN up.","docs":"https://skill.whisk.run/errors/CDN_UNAVAILABLE","details":{}}}CDNKEYINVALID
Status: 400 · Surface: api, dashboard
When: the operator set the platform's CDN provider (PUT /v1/operator/cdn/bunny) with a key that cannot be used: it is empty, holds spaces, line breaks or characters an API key never has, or Bunny does not accept it. details.reason is format or refused.
Fix: Copy the API key from https://dash.bunny.net/account/api-key and paste it whole.
{"error":{"code":"CDN_KEY_INVALID","message":"Bunny did not accept that API key.","fix":"Copy the API key from https://dash.bunny.net/account/api-key and paste it whole.","docs":"https://skill.whisk.run/errors/CDN_KEY_INVALID","details":{"reason":"refused"}}}CDNINUSE
Status: 409 · Surface: api, dashboard
When: the operator tried to remove the CDN provider's key while apps are still served through it. Their addresses point at the provider, so removing the key would leave Whisk unable to move them back.
Fix: Turn the CDN off for the apps in details.apps, wait until each shows off, then remove the key.
{"error":{"code":"CDN_IN_USE","message":"2 apps are still served through the CDN.","fix":"Turn the CDN off for those apps, wait until each shows off, then remove the key.","docs":"https://skill.whisk.run/errors/CDN_IN_USE","details":{"apps":["acme/crm","acme/site"]}}}GITHUBNOTSET_UP
Status: 503 · Surface: api, cli, dashboard
When: an app's copy on GitHub was asked for on a platform whose operator has not set up Whisk's GitHub App yet (CONTROL-PLANE.md §6.26).
Fix: Nothing to change in the app. Apps run as before without GitHub; the operator sets the App up on the operator page.
{"error":{"code":"GITHUB_NOT_SET_UP","message":"Copying apps to GitHub is not available on this platform yet.","fix":"The operator sets up Whisk's GitHub App on the operator page first.","docs":"https://skill.whisk.run/errors/GITHUB_NOT_SET_UP"}}GITHUBNOTINSTALLED
Status: 409 · Surface: api, cli, dashboard
When: an app was linked to a GitHub repository, but Whisk is not installed on any GitHub account of the business yet.
Fix: An owner or admin chooses Connect GitHub in the app's settings and installs Whisk on the GitHub account that holds the repository, then links it.
{"error":{"code":"GITHUB_NOT_INSTALLED","message":"Whisk is not installed on any GitHub account of this business yet.","fix":"An owner or admin chooses Connect GitHub in the app's settings and installs Whisk on the account that holds the repository.","docs":"https://skill.whisk.run/errors/GITHUB_NOT_INSTALLED"}}GITHUBINSTALLATIONUNVERIFIED
Status: 403 · Surface: dashboard
When: GitHub sent a person back from installing Whisk, but their own GitHub account cannot see the installation named, so it may not be theirs to connect.
Fix: Start again from Connect GitHub in Whisk, signed in to GitHub as someone who can manage the account Whisk is installed on.
{"error":{"code":"GITHUB_INSTALLATION_UNVERIFIED","message":"Your GitHub account cannot see that installation of Whisk.","fix":"Install Whisk on GitHub from the app's settings in Whisk, signed in to GitHub as someone who can manage that account.","docs":"https://skill.whisk.run/errors/GITHUB_INSTALLATION_UNVERIFIED","details":{"installation_id":51234567}}}GITHUBINSTALLATIONTAKEN
Status: 409 · Surface: dashboard
When: the GitHub account Whisk was installed on is already connected to another business on Whisk.
Fix: Use another GitHub account or organisation, or remove the connection from the other business first.
{"error":{"code":"GITHUB_INSTALLATION_TAKEN","message":"That GitHub account is already connected to another business on Whisk.","fix":"Use another GitHub account or organisation, or remove the connection from the other business first.","docs":"https://skill.whisk.run/errors/GITHUB_INSTALLATION_TAKEN","details":{"account":"acme"}}}GITHUBREPOUNAVAILABLE
Status: 422 · Surface: api, cli, dashboard
When: the repository to link does not exist, or none of the business's installations of Whisk on GitHub may reach it.
Fix: Check the name, add the repository to Whisk's installation on GitHub (the app's settings link to the page), then link again.
{"error":{"code":"GITHUB_REPO_UNAVAILABLE","message":"Whisk cannot reach acme/crm on GitHub.","fix":"Check the name, and add the repository to Whisk's installation on GitHub (the app's settings link to the page), then link again.","docs":"https://skill.whisk.run/errors/GITHUB_REPO_UNAVAILABLE","details":{"repo":"acme/crm"}}}GITHUBREPOTAKEN
Status: 409 · Surface: api, cli, dashboard
When: the repository is already linked to another app.
Fix: Pick another repository, or unlink it from the other app first.
{"error":{"code":"GITHUB_REPO_TAKEN","message":"acme/crm is already linked to the app crm.","fix":"Pick another repository, or unlink it from crm first.","docs":"https://skill.whisk.run/errors/GITHUB_REPO_TAKEN","details":{"repo":"acme/crm","app":"crm"}}}GITHUBREPONOT_EMPTY
Status: 409 · Surface: api, cli, dashboard
When: the repository already holds code that shares no history with the app, so linking would mix two unrelated projects.
Fix: Link an empty repository (created on GitHub without a README), or one that already holds this app's code.
{"error":{"code":"GITHUB_REPO_NOT_EMPTY","message":"acme/website already holds other code.","fix":"Link an empty repository (create one on GitHub without a README), or one that already holds this app's code.","docs":"https://skill.whisk.run/errors/GITHUB_REPO_NOT_EMPTY","details":{"repo":"acme/website"}}}LOGDESTINATIONFAILED
Status: 422 · Surface: api
When: a log destination did not accept the test line Whisk sends when an owner adds one, or when someone asks for a test: it answered with a status outside 200 to 299, did not answer within 10 seconds, does not resolve, or resolves to an address that is not public. details.status is the status it answered (0 when it did not answer) and details.answer the start of what it said. Nothing is saved when adding fails.
Fix: Check the URL (or the Datadog site), the headers and the key with the log service, then add the destination again.
{"error":{"code":"LOG_DESTINATION_FAILED","message":"logs.example.com answered 401 to a test line.","fix":"Check the URL, the headers and the key with your log service, then add the destination again.","docs":"https://skill.whisk.run/errors/LOG_DESTINATION_FAILED","details":{"status":401,"answer":"{\"error\":\"invalid token\"}"}}}STORAGE_QUOTA
Status: 402 · Surface: api
When: the org's storage is at its plan limit on a plan that does not bill storage past it (the free app, an Agency client business, a business with no card on file, or a paid plan during its free trial); new uploads are refused, reads continue.
Fix: Delete objects you no longer need, or ask an owner to upgrade.
{"error":{"code":"STORAGE_QUOTA","message":"acme is using 1.0 GB of its 1.0 GB storage allowance.","fix":"Delete objects under the app's prefix you no longer need, or ask an owner to upgrade at https://whisk.run/o/acme/billing.","docs":"https://skill.whisk.run/errors/STORAGE_QUOTA","details":{"limit_bytes":1073741824,"used_bytes":1073741824}}}UPLOADTOOLARGE
Status: 413 · Surface: api
When: an upload form was asked for with a max_bytes above the 5 GB one object may be. A file larger than the max_bytes a form was issued for is refused by the object store itself, which answers EntityTooLarge.
Fix: Ask for a form with max_bytes at or under the limit in details, or split the file.
{"error":{"code":"UPLOAD_TOO_LARGE","message":"An upload may be at most 5 GB; this form asked for 6.0 GB.","fix":"Ask for a form with max_bytes up to 5368709120, or split the file.","docs":"https://skill.whisk.run/errors/UPLOAD_TOO_LARGE","details":{"max_bytes":6442450944,"limit_bytes":5368709120}}}OBJECT_QUARANTINED
Status: 410 · Surface: api
When: the virus scan flagged an uploaded object; it was moved to quarantine/ under the app's prefix and a notification was sent.
Fix: Treat the upload as rejected in the app. An admin can inspect or delete it from the dashboard.
{"error":{"code":"OBJECT_QUARANTINED","message":"uploads/invoice-4412.pdf was quarantined: Eicar-Test-Signature.","fix":"Show the uploader that the file was rejected. An admin can review it at https://whisk.run/o/acme/apps/job-tracker/settings.","docs":"https://skill.whisk.run/errors/OBJECT_QUARANTINED","details":{"key":"uploads/invoice-4412.pdf","signature":"Eicar-Test-Signature","quarantine_key":"quarantine/uploads/invoice-4412.pdf"}}}MEDIANOTREADY
Status: 409 · Surface: api, edge
When: a converted copy of an uploaded video or audio file was asked for at /.whisk/media/<id>/<file> before the conversion made it, or the conversion failed or is held until the month's conversion allowance renews. The original plays meanwhile at /.whisk/media/<id>/original.
Fix: Read /.whisk/media/<id> for the status and the sources that exist, or use the player (/.whisk/player.js), which plays the original until the copies are ready.
{"error":{"code":"MEDIA_NOT_READY","message":"site-walkthrough.mov is still being converted; 720.mp4 is not ready.","fix":"Read /.whisk/media/01J9ABC for what exists, or play /.whisk/media/01J9ABC/original until it is ready.","docs":"https://skill.whisk.run/errors/MEDIA_NOT_READY","details":{"id":"01J9ABC","status":"converting","file":"720.mp4"}}}IMAGENOTREADY
Status: 409 · Surface: edge
When: an uploaded image was asked for at /.whisk/img/<id> before its file reached the store: the upload's form was issued but the browser's POST has not finished. The answer carries Retry-After: 5 and is never cached.
Fix: Ask again once the upload has finished; the address is right and stays right.
{"error":{"code":"IMAGE_NOT_READY","message":"team-photo.jpg has not finished uploading.","fix":"Ask again once the upload's POST to the store has finished; the address is right.","docs":"https://skill.whisk.run/errors/IMAGE_NOT_READY","details":{"id":"01J9ABC"}}}IMAGE_UNREADABLE
Status: 422 · Surface: edge
When: an upload authorised as an image could not be read as one by the resizer: the file is damaged, is in a format imgproxy does not read, or is larger than 50 megapixels. It is recorded on the upload, so later requests answer at once, and GET …/uploads/<id> shows it as image.detail.
Fix: Show the uploader that the picture could not be used and ask for it again as a JPEG, PNG, WebP, AVIF, GIF or HEIC file under 50 megapixels.
{"error":{"code":"IMAGE_UNREADABLE","message":"scan.tiff: The file could not be read as an image, or is larger than 50 megapixels.","fix":"Upload the picture again as a JPEG, PNG, WebP, AVIF, GIF or HEIC file under 50 megapixels.","docs":"https://skill.whisk.run/errors/IMAGE_UNREADABLE","details":{"id":"01J9ABC","content_type":"image/tiff"}}}NOTANIMAGE
Status: 415 · Surface: edge
When: /.whisk/img/<id> names an upload whose content_type is not image/*, such as a PDF or a video.
Fix: Resize only uploads authorised with an image content type. Play video and audio at /.whisk/media/<id>, and give other files out with GET …/uploads/<id>.
{"error":{"code":"NOT_AN_IMAGE","message":"invoice-4412.pdf is application/pdf, not an image.","fix":"Use /.whisk/img/ for uploads authorised with an image/* content_type; play video and audio at /.whisk/media/<id>.","docs":"https://skill.whisk.run/errors/NOT_AN_IMAGE","details":{"id":"01J9ABC","content_type":"application/pdf"}}}EXPORTINPROGRESS
Status: 409 · Surface: api, cli
When: an export job is already running for the org.
Fix: Wait for it; whisk export --wait follows the existing job.
{"error":{"code":"EXPORT_IN_PROGRESS","message":"An export started at 2026-09-07T10:02:00Z is still running.","fix":"Run whisk export --wait to follow it; a new export can start when it finishes.","docs":"https://skill.whisk.run/errors/EXPORT_IN_PROGRESS","details":{"job_id":"01J9","started_at":"2026-09-07T10:02:00Z"}}}EXPORT_FAILED
Status: - · Surface: api (the export's record), cli
When: an export could not be written: a node did not answer, a dump failed, or the org's bucket refused the archive. details.piece names where it stopped, details.node the node when one was involved, and details.cause_code the node's own code when it gave one.
Fix: Start the export again. If it stops at the same piece, report it with whisk feedback --kind bug --code EXPORT_FAILED, naming the export's id and details.piece.
{"error":{"code":"EXPORT_FAILED","message":"The export stopped at apps/notes/databases/production.dump on node node1: pg_dump app_01: exited 1","fix":"Start the export again. If it stops at the same piece, report it with whisk feedback --kind bug --code EXPORT_FAILED --message \"export <id> stopped at <piece>\".","docs":"https://skill.whisk.run/errors/EXPORT_FAILED","details":{"piece":"apps/notes/databases/production.dump","node":"node1","cause":"pg_dump app_01: exited 1","cause_code":"DATABASE_FAILED"}}}RESTORENOTAVAILABLE
Status: 402 · Surface: api, cli
When: self-service point-in-time restore was requested on a plan without it, or for a time outside the retention window.
Fix: Choose a time within the last 30 days, or ask an owner to upgrade. The platform's own backups still cover the org.
{"error":{"code":"RESTORE_NOT_AVAILABLE","message":"Self-service restore is not included in the Starter plan.","fix":"Ask an owner to upgrade at https://whisk.run/o/acme/billing, or contact support@whisk.run for an operator-assisted restore.","docs":"https://skill.whisk.run/errors/RESTORE_NOT_AVAILABLE","details":{"plan":"starter","retention_days":30}}}RESTOREINPROGRESS
Status: 409 · Surface: api, cli
When: a self-service restore was requested for an app that already has one queued or running. Two restores of one database race for its name, so one runs at a time.
Fix: Wait for the running restore to finish: whisk restore show <id> --wait follows it (GET /orgs/:org/apps/:app/restores/:id), and whisk restore list lists the app's restores.
{"error":{"code":"RESTORE_IN_PROGRESS","message":"A restore started at 2026-09-16T10:02:00Z is still running.","fix":"Wait for it to finish: whisk restore show 01J9 --wait follows it (GET /orgs/:org/apps/:app/restores/01J9).","docs":"https://skill.whisk.run/errors/RESTORE_IN_PROGRESS","details":{"restore":"01J9","started_at":"2026-09-16T10:02:00Z"}}}APPNOTRESTORABLE
Status: 409 · Surface: api, cli
When: whisk apps restore <id> named an app that is not deleted, whose 7 days are up and whose data was released, or whose slug another app in the business now uses.
Fix: whisk apps deleted lists the apps that can still be restored. For a slug in use, delete or rename the other app first. A released app cannot be brought back from here; if it was deleted by mistake within the last 30 days, ask with whisk feedback.
{"error":{"code":"APP_NOT_RESTORABLE","message":"crm is not a deleted app that can still be restored: an app is restorable for 7 days after it is deleted, until its data is released.","fix":"Nothing can bring it back from here. If it was deleted by mistake less than 30 days ago, ask Whisk's support with whisk feedback.","docs":"https://skill.whisk.run/errors/APP_NOT_RESTORABLE","details":{"app":"01J9","status":"deleted"}}}RESOURCE_TAKEN
Status: 409 · Surface: api
When: a provider answered with a thing the platform's record already holds for another business: the same id at the same provider and account (a bucket, a sending domain, a GitHub installation). The platform never records one thing as two businesses', because releasing it for one would delete it for the other (CONTROL-PLANE.md §6.32).
Fix: Use another one, or ask its business to remove it first.
{"error":{"code":"RESOURCE_TAKEN","message":"That github installation (51234) already belongs to another business on Whisk.","fix":"Use another one, or ask its business to remove it first.","docs":"https://skill.whisk.run/errors/RESOURCE_TAKEN","details":{"kind":"github_installation","id":"51234"}}}EMAILDOMAINTAKEN
Status: 409 · Surface: api, cli
When: a business adds a sending domain another business on Whisk already sends from, or one of Whisk's own domains or a name under one. A sending domain belongs to one business, so removing it from one never stops another's mail or Whisk's.
Fix: Send from a domain only your business uses, such as a subdomain like mail.yourbusiness.com. If the domain is yours, remove it from the other business first.
{"error":{"code":"EMAIL_DOMAIN_TAKEN","message":"mail.acme.com is already a sending domain of another business on Whisk.","fix":"Send from a domain only your business uses, such as a subdomain like mail.yourbusiness.com. If the domain is yours, remove it from the other business first.","docs":"https://skill.whisk.run/errors/EMAIL_DOMAIN_TAKEN","details":{"domain":"mail.acme.com"}}}RESOURCESNOTRELEASED
Status: 409 · Surface: api (operator), cli
When: a business is being shredded, or an app's release ran, and something the business owns outside the platform's database is not released yet: a provider did not answer or refused. The business is not marked shredded until every such thing is released or retained on purpose. A thing a lookup found, which waits on an operator's confirmation, never causes it: the release finishes without it and the thing stays listed.
Fix: Nothing to do now: the release is tried again on its own, with backoff, and every hour after that. GET /v1/operator/orgs/:org/resources (whisk operator resources <business>) lists each one with its last error.
{"error":{"code":"RESOURCES_NOT_RELEASED","message":"2 of the business's outside things are not released yet (bucket, ses_identity).","fix":"Nothing to do now: the release is tried again on its own, with backoff, and every hour after that. GET /v1/operator/orgs/:org/resources lists each one with its last error.","docs":"https://skill.whisk.run/errors/RESOURCES_NOT_RELEASED","details":{"open":2,"stuck":0,"kinds":"bucket, ses_identity"}}}RESTOREPOINTNOT_ARCHIVED
Status: 503 · Surface: api, cli
When: whisk db snapshot, or whisk db query --confirm on production, took a restore point that did not reach the backup archive within 90 seconds, so a restore could not reach it. Nothing was changed: the confirmed SQL did not run. A deploy whose migration meets the same fails with MIGRATE_FAILED (details.step snapshot) before the migration runs.
Fix: Wait a few minutes and run the command again (a confirmed query needs a fresh preview and code). This is on Whisk's side; if it repeats, send it with whisk feedback.
{"error":{"code":"RESTORE_POINT_NOT_ARCHIVED","message":"The restore point did not reach the backup archive, so it could not be restored to. Nothing was changed.","fix":"Wait a few minutes and run the command again; a confirmed query needs a fresh preview and code. If it repeats, send it with whisk feedback.","docs":"https://skill.whisk.run/errors/RESTORE_POINT_NOT_ARCHIVED","details":{"restore_point":"snapshot-01J9-20261006T101500Z","lsn":"0/3000060"}}}DBACCESSOFF
Status: 403 · Surface: api, cli
When: whisk db query, whisk db schema, whisk db shell or whisk db url was used in an org whose owners turned database access from outside the app off (settings.db_access = false).
Fix: Run the query from inside the app, or ask an owner or admin to turn database access on under the org's settings.
{"error":{"code":"DB_ACCESS_OFF","message":"Database access from laptops is turned off for acme.","fix":"An owner or admin can turn it on under settings, or run the query from inside the app.","docs":"https://skill.whisk.run/errors/DB_ACCESS_OFF","details":{"setting":"db_access"}}}CONFIRM_REQUIRED
Status: 409 · Surface: api, cli
When: whisk db query was given SQL that would change or remove existing data: update or delete rows (upserts and cascades included), drop, alter, truncate or revoke something, or create or replace a definition. The platform ran it in a transaction, recorded its effect and rolled it back, so nothing was changed. details carries the effect (per statement, and rows inserted, updated and deleted per table), the code and when it expires.
Fix: Check the effect is what you meant, then run whisk db query --confirm <code> within ten minutes. A restore point is taken first, and the SQL commits only if it does the same again. Whether that restore point can be returned to depends on the plan: the fix says so, and details.restorable is true where whisk restore reaches it (within the plan's restore window, 7 days on Starter, Team and Agency and 30 on Business) and false on Free, where nothing undoes the change and the rows it touches should be copied first with whisk db query "select ..." --json.
{"error":{"code":"CONFIRM_REQUIRED","message":"Nothing was changed yet. This would delete 48 rows in public.orders in the production database.","fix":"Check that this is what you meant, then run whisk db query --confirm wq_mfrgg4dfmzxw2 before 2026-09-29T12:10:00Z to do it; a restore point is taken first, and whisk restore --at <its time> returns to it for the next 7 days.","docs":"https://skill.whisk.run/errors/CONFIRM_REQUIRED","details":{"code":"wq_mfrgg4dfmzxw2","expires_at":"2026-09-29T12:10:00Z","environment":"production","database":"app_a1","restorable":true,"effect":{"statements":[{"command":"DELETE 48","rows":48}],"tables":[{"table":"public.orders","inserted":0,"updated":0,"deleted":48}]}}}}CONFIRM_INVALID
Status: 409 · Surface: api, cli
When: whisk db query --confirm named a code that does not exist, was already used, is more than ten minutes old, or was issued to someone else.
Fix: Run the SQL again without --confirm for a fresh preview and code.
{"error":{"code":"CONFIRM_INVALID","message":"That code is unknown, used, expired or someone else's.","fix":"Run the SQL again without --confirm for a fresh preview and code.","docs":"https://skill.whisk.run/errors/CONFIRM_INVALID","details":{}}}CONFIRM_CHANGED
Status: 409 · Surface: api, cli
When: a confirmed whisk db query would now do something other than its preview said, because the data changed in between. It was rolled back; nothing was changed. The code is used up.
Fix: Run the SQL again without --confirm for a fresh preview and code.
{"error":{"code":"CONFIRM_CHANGED","message":"Nothing was changed: the SQL would now delete 50 rows in public.orders, not delete 48 rows in public.orders as the preview said.","fix":"Run the SQL again without --confirm for a fresh preview and code.","docs":"https://skill.whisk.run/errors/CONFIRM_CHANGED","details":{}}}QUERY_FAILED
Status: 422 · Surface: api, cli
When: PostgreSQL refused the SQL whisk db query sent: a syntax error, a missing table, a constraint, a statement past the 30-second limit (57014), a lock held for more than five seconds (55P03), or a statement that cannot run inside a transaction. details.sqlstate is PostgreSQL's own code. The transaction was rolled back; nothing was changed.
Fix: Read PostgreSQL's message, fix the SQL and run it again.
{"error":{"code":"QUERY_FAILED","message":"PostgreSQL refused the SQL: relation \"ordrs\" does not exist.","fix":"Fix the SQL and run it again. Nothing was changed.","docs":"https://skill.whisk.run/errors/QUERY_FAILED","details":{"sqlstate":"42P01","position":15}}}QUERYRESULTTOO_LARGE
Status: 422 · Surface: api, cli
When: whisk db query was stopped because the database sent more than the control plane reads for one call: a single row, error or notice larger than 8 MiB (details.kind is message), or more than 256 MiB of rows across the SQL's results (details.kind is rows), counting rows past limit that are counted but not answered. The connection was dropped, which rolls the transaction back; nothing was changed.
Fix: Narrow what the SQL returns: a where clause or a limit, count(*) to count, and left(col, 1000) or length(col) for a very large value.
{"error":{"code":"QUERY_RESULT_TOO_LARGE","message":"The database sent a single row or message larger than 8 MiB, so the query was stopped. Nothing was changed.","fix":"Select fewer or shorter columns, for example left(body, 1000) or length(body) instead of body, and run it again.","docs":"https://skill.whisk.run/errors/QUERY_RESULT_TOO_LARGE","details":{"limit_bytes":8388608,"kind":"message"}}}QUERY_BUSY
Status: 429 · Surface: api, cli
When: whisk db query or whisk db schema waited five seconds for a place and did not start: the org already had two of them running, or the platform sixteen. Nothing ran.
Fix: Wait for the running queries to finish and send it again (details.retry_after seconds); run queries one after another rather than at once.
{"error":{"code":"QUERY_BUSY","message":"The org already has 2 database queries running, or the platform has 16; this one waited 5s and did not start.","fix":"Wait for the running queries to finish, then send it again; run queries one after another rather than at once.","docs":"https://skill.whisk.run/errors/QUERY_BUSY","details":{"per_org":2,"at_once":16,"retry_after":5}}}DBURLPRIVATE
Status: - · Surface: cli
When: whisk db url was run where the platform runs no public database proxy, so the connection string's address is inside the app's network and does not answer from outside.
Fix: Use whisk db query "<sql>" or whisk db schema, which run on the platform. --private prints the inside address anyway, for use from inside the platform.
{"error":{"code":"DB_URL_PRIVATE","message":"The production database's address is inside the platform, so it does not answer from this machine.","fix":"Use whisk db query \"<sql>\" for rows as JSON or whisk db schema for the tables; both run on the platform.","docs":"https://skill.whisk.run/errors/DB_URL_PRIVATE","details":{"environment":"production"}}}PLATFORM_UNAVAILABLE
Status: 503 · Surface: api, edge, cli
When: a platform component is down. The response is temporary and safe to retry. The CLI answers with it when it could not reach the platform, when a download or stream was cut off, or when a successful answer was not the API's JSON (a proxy or captive portal answered instead); it has already retried a call that was safe to (CLI.md §1) and exits 5.
Fix: Retry with backoff. Status is at https://whisk.run/status.
{"error":{"code":"PLATFORM_UNAVAILABLE","message":"The platform is temporarily unavailable.","fix":"Retry with backoff. See https://whisk.run/status for the current status.","docs":"https://skill.whisk.run/errors/PLATFORM_UNAVAILABLE","details":{"retry_after":10}}}NODE_DOWN
Status: 503 · Surface: api, edge, cli
When: the node hosting the app is not answering. The operator has been paged; apps on other nodes are unaffected. From whisk db query and whisk db schema it means the environment's database did not answer the connection at all; a database that answers and refuses the password is DATABASE_CREDENTIALS_REJECTED instead.
Fix: Wait. Nothing the app can do; deploys are refused until the node is back or the app has been moved.
{"error":{"code":"NODE_DOWN","message":"The node hosting job-tracker has not been seen for 3 minutes.","fix":"Wait; the operator has been paged. See https://whisk.run/status.","docs":"https://skill.whisk.run/errors/NODE_DOWN","details":{"node":"node1","last_seen_at":"2026-09-07T10:00:00Z"}}}NODEDRAINNO_TARGET
Status: 409 · Surface: api
When: the operator drains a node and at least one app live on it has no other ready node to move to. Nothing is moved and the node's status is unchanged: a half-drained node would still need the machine.
Fix: Enrol another node, or bring a down node back, then drain again.
{"error":{"code":"NODE_DRAIN_NO_TARGET","message":"No other node is ready to take job-tracker, so node1 cannot be drained.","fix":"Enrol or bring back another ready node, then drain again.","docs":"https://skill.whisk.run/errors/NODE_DRAIN_NO_TARGET","details":{"node":"node1","apps":["job-tracker"]}}}LICENCE_MISSING
Status: 403 · Surface: api, git, deploy
When: Whisk On-Premise has no licence installed, so a deploy or a push is refused. Running apps keep running. The hosted platform never answers it.
Fix: Ask Whisk for your licence, then install it on the operator page or with whiskd licence install < licence.txt.
{"error":{"code":"LICENCE_MISSING","message":"No Whisk licence is installed, so nothing new can deploy.","fix":"Ask Whisk for a current licence, then install it on the operator page or with `whiskd licence install < licence.txt`.","docs":"https://skill.whisk.run/errors/LICENCE_MISSING","details":{}}}LICENCE_INVALID
Status: 403 · Surface: api, git, deploy
When: the installed Whisk On-Premise licence cannot be used: its signature does not match its terms (the file was changed after Whisk issued it), it was signed with a key this version does not know, or its start date is still ahead. Installing such a file is refused with the same code.
Fix: Install the licence file exactly as Whisk sent it, from its first line to its signature. If it is unchanged, ask Whisk for a current licence.
{"error":{"code":"LICENCE_INVALID","message":"The installed Whisk licence cannot be used: The licence signature does not match its terms. The file has been changed since Whisk issued it.","fix":"Ask Whisk for a current licence, then install it on the operator page or with `whiskd licence install < licence.txt`.","docs":"https://skill.whisk.run/errors/LICENCE_INVALID","details":{"problem":"The licence signature does not match its terms. The file has been changed since Whisk issued it."}}}LICENCE_EXPIRED
Status: 403 · Surface: api, git, deploy
When: the Whisk On-Premise licence has ended. Running apps keep running and keep their data; new deploys and pushes wait for a current licence.
Fix: Ask Whisk to renew the licence, then install the new file on the operator page or with whiskd licence install < licence.txt. Deploys work again at once.
{"error":{"code":"LICENCE_EXPIRED","message":"The Whisk licence ended on 2027-10-08. Running apps keep running; new deploys wait for a current licence.","fix":"Ask Whisk for a current licence, then install it on the operator page or with `whiskd licence install < licence.txt`.","docs":"https://skill.whisk.run/errors/LICENCE_EXPIRED","details":{"licence":"lic_2026_0001","ended":"2027-10-08"}}}REPOTOOLARGE
Status: 422 · Surface: git, cli
When: the repository exceeds the plan's size limit after the push.
Fix: Remove large files from history (build output, media, archives) and use storage for files. whisk doctor reports the size before pushing.
{"error":{"code":"REPO_TOO_LARGE","message":"The repository would be 1.4 GB; the Team plan allows 500 MB.","fix":"Remove build output and media from the repository and its history, ignore them in .gitignore, and use storage for files. Then push again.","docs":"https://skill.whisk.run/errors/REPO_TOO_LARGE","details":{"limit_bytes":524288000,"size_bytes":1503238553,"largest":[{"path":"assets/video.mp4","bytes":812000000}]}}}SOURCE_CHANGED
Status: 409 · Surface: api
When: files were sent to deploy (POST /apps/:app/source, the assistant connector's deploy_app) with a base_commit that is no longer the tip of the app's default branch, or another push landed while this one was being made. Someone else changed the app since its files were read.
Fix: Read the app's files again (read_app_files), apply the change to what is there now, and send it with the new commit as base_commit.
{"error":{"code":"SOURCE_CHANGED","message":"The app changed since its files were read: the default branch is now at 3f9c2a1b7d0e.","fix":"Read the app's files again, apply your change to them, and deploy with base_commit 3f9c2a1b7d0e.","docs":"https://skill.whisk.run/errors/SOURCE_CHANGED","details":{"commit":"3f9c2a1b7d0e4c55a1b2c3d4e5f60718293a4b5c","base_commit":"9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0b"}}}INVALID_REQUEST
Status: 400 · Surface: api, hooks
When: the request body or parameters do not match what the endpoint expects: malformed JSON, a missing field, a wrong type.
Fix: Fix the request as described in details.problems.
{"error":{"code":"INVALID_REQUEST","message":"The event is missing name.","fix":"Send {\"name\": \"<event name>\", \"data\": {...}} with an optional dedupe_key.","docs":"https://skill.whisk.run/errors/INVALID_REQUEST","details":{"problems":[{"path":"/name","message":"required"}]}}}CONFLICT
Status: 409 · Surface: api
When: another request created or changed the same thing at the same moment, so this one could not be applied as sent.
Fix: Read the current state and retry the request if it still applies.
{"error":{"code":"CONFLICT","message":"Another request changed this at the same moment.","fix":"Read the current state and retry the request if it still applies.","docs":"https://skill.whisk.run/errors/CONFLICT"}}ROLLBACK_UNAVAILABLE
Status: 409 · Surface: api
When: an operator asked to roll the control plane back, and the copy it replaced cannot take over now: the 10 minutes it is kept warm after a switch have passed, it is not running idle and healthy, or it did not start serving and the switch was undone. details.reason says which.
Fix: Read GET /v1/operator/control-plane for each copy's state. After the 10 minutes, release the earlier version instead.
{"error":{"code":"ROLLBACK_UNAVAILABLE","message":"The control plane cannot be rolled back now: the 10 minutes after the switch have passed.","fix":"Read GET /v1/operator/control-plane for each copy's state. After the 10 minutes, release the earlier version instead.","docs":"https://skill.whisk.run/errors/ROLLBACK_UNAVAILABLE","details":{"reason":"expired","live":"green","epoch":5}}}NOT_FOUND
Status: 404 · Surface: api, edge
When: the org, app, deploy, run, secret or other resource in the URL does not exist or is not visible to this token. On the edge: the hostname belongs to no app (a removed preview, a deleted app, a typo under the apps domain); every name under a wildcard certificate reaches the edge, so the edge answers this rather than an empty response. details.kind is hostname. Also on the edge: a path only an attacker's scanner asks for (WordPress, PHP, secrets or version control files such as /.env and .git), which nothing on Whisk serves; details.kind is path.
Fix: Check the identifiers with the matching list command; whisk use <org>/<app> fixes a directory bound to the wrong app.
{"error":{"code":"NOT_FOUND","message":"No app named job-trakcer in org acme.","fix":"Run whisk apps list to see the app slugs, then whisk use acme/<slug>.","docs":"https://skill.whisk.run/errors/NOT_FOUND","details":{"kind":"app","org":"acme","slug":"job-trakcer"}}}NEEDS_HUMAN
Status: - · Surface: cli
When: a CLI command reached a point only a human can complete: login approval, setting a secret value, connecting an account, confirming the org. The CLI prints the URL and exits 2. After a deploy, a NEEDS_HUMAN block lists unset secrets even though the deploy succeeded.
Fix: Show the human the URL and the names exactly as printed, then wait or continue with work that does not depend on it.
{"error":{"code":"NEEDS_HUMAN","message":"2 secrets need a value before the app can use them: XERO_CLIENT_SECRET, SLACK_WEBHOOK_URL.","fix":"Ask an owner or admin to set them at https://whisk.run/o/acme/secrets. The app restarts automatically when they are set.","docs":"https://skill.whisk.run/errors/NEEDS_HUMAN","details":{"url":"https://whisk.run/o/acme/secrets","names":["XERO_CLIENT_SECRET","SLACK_WEBHOOK_URL"]}}}UPDATE_FAILED
Status: - · Surface: cli
When: whisk update could not install the release the platform serves: the download does not match SHA256SUMS, SHA256SUMS.sig does not verify under the release key the build embeds, the release has no binary for this platform, or the running binary could not be replaced. details.asset names the file; details.path the binary.
Fix: Run whisk update again. If the checksum or signature fails the same way, do not install it (the binary you have keeps working) and report it with whisk feedback --kind bug --code UPDATE_FAILED; if the binary cannot be replaced, make its directory writable or reinstall with the install script.
{"error":{"code":"UPDATE_FAILED","message":"The downloaded whisk_linux_amd64 does not match SHA256SUMS.","fix":"Run whisk update again; if it fails the same way the release on the platform is inconsistent: do not install it, and report it with whisk feedback --kind bug --code UPDATE_FAILED.","docs":"https://skill.whisk.run/errors/UPDATE_FAILED","details":{"asset":"whisk_linux_amd64","want":"9f3c…","got":"1a2b…"}}}LOCALDOCKERUNAVAILABLE
Status: - · Surface: cli
When: whisk dev needs Docker with the compose plugin to start Postgres, the workflow engine and the local edge, and Docker is not installed, not running, or has no compose plugin. Nothing was started. The CLI exits 1.
Fix: Install Docker Desktop or Docker Engine with the compose plugin and start it, then run whisk dev again. Without Docker, run the app under whisk-stub (contract/cmd/whisk-stub) with a Postgres of your own. Retrying without Docker does not help.
{"error":{"code":"LOCAL_DOCKER_UNAVAILABLE","message":"docker compose version: exec: \"docker\": executable file not found in $PATH","fix":"Install Docker Desktop or Docker Engine with the compose plugin and start it, then run whisk dev again; or run the app under whisk-stub with your own Postgres. Retrying without Docker will not help.","docs":"https://skill.whisk.run/errors/LOCAL_DOCKER_UNAVAILABLE","details":{}}}DEVSTACKFAILED
Status: - · Surface: cli
When: whisk dev found Docker but docker compose up for the local stack in .whisk/dev/ failed: a port it needs is taken, an image could not be pulled, or a container would not start. message carries compose's own words. The CLI exits 1.
Fix: Check that Docker is running and that the ports in .whisk/dev/compose.yaml are free, then run whisk dev again. whisk dev --down resets the stack; --down --wipe also deletes its database.
{"error":{"code":"DEV_STACK_FAILED","message":"docker compose up: Bind for 127.0.0.1:5432 failed: port is already allocated","fix":"Check that Docker is running and the ports in .whisk/dev/compose.yaml are free, then run whisk dev again; whisk dev --down resets the stack.","docs":"https://skill.whisk.run/errors/DEV_STACK_FAILED","details":{}}}GITTLSUNTRUSTED
Status: - · Surface: cli
When: whisk deploy pushed and git refused the certificate of the platform's git host ("SSL certificate problem: unable to get local issuer certificate" and the like). On Windows the CLI first retries the push once with -c http.sslBackend=schannel, so Git for Windows verifies with the Windows certificate store instead of its bundled OpenSSL roots; this code means that retry failed too, or the machine is not Windows. details.host names the host and details.git holds git's last lines.
Fix: On Windows, run git config --global http.sslBackend schannel and deploy again. If that does not help, or elsewhere, an HTTPS-inspecting proxy or antivirus is presenting its own certificate: add its root to the certificate store git uses (or point http.sslCAInfo at a bundle that includes it), then deploy again.
{"error":{"code":"GIT_TLS_UNTRUSTED","message":"git does not trust the certificate of git.whisk.run.","fix":"Update the system CA certificates. If an HTTPS-inspecting proxy or antivirus is in the path, point git's http.sslCAInfo at a bundle that includes its root certificate, then run whisk deploy again.","docs":"https://skill.whisk.run/errors/GIT_TLS_UNTRUSTED","details":{"host":"git.whisk.run","git":["fatal: unable to access 'https://git.whisk.run/01J/a1.git/': SSL certificate problem: unable to get local issuer certificate"]}}}UNKNOWN_COMMAND
Status: - · Surface: cli
When: the CLI was given a command or flag this build does not have. The usual cause is a CLI installed before the command shipped while the docs or skill describe the current one. details.whisk_cli is the running version.
Fix: Run whisk update to install the CLI the platform serves, then retry. If the command is still unknown, check the spelling against whisk --help.
{"error":{"code":"UNKNOWN_COMMAND","message":"This whisk (pilot-66313c9) has no such command or flag: unknown command \"db\" for \"whisk\".","fix":"Run whisk update to install the platform's current CLI, then retry; run whisk --help for what this build offers.","docs":"https://skill.whisk.run/errors/UNKNOWN_COMMAND","details":{"whisk_cli":"pilot-66313c9"}}}CLI_ERROR
Status: - · Surface: cli
When: the CLI failed on this computer for a reason no other code names: a file it could not read or write, a directory it could not create, a git command that could not start. The message is the underlying error as the system gave it. A failure to reach the platform, or an answer that did not come from it, is PLATFORM_UNAVAILABLE instead. The CLI exits 1.
Fix: Read the message and fix what it names on this computer (a permission, a full disk, a missing program); if it names a platform problem, retry with backoff. If it names neither, report it with whisk feedback --kind bug --code CLI_ERROR and the message.
{"error":{"code":"CLI_ERROR","message":"open whisk.yaml: permission denied","fix":"Read the message; if it names a platform problem, retry with backoff.","docs":"https://skill.whisk.run/errors/CLI_ERROR"}}APPOUTOF_MEMORY
Status: - · Surface: container, run
When: the app's previous run was killed with SIGKILL that no stop preceded, which on Whisk means it used more memory than its container's limit (files in /tmp count as memory). Under gVisor the kernel kills the whole sandbox, so the app itself prints nothing more. At the moment of the kill the platform writes a line into the app's logs (whisk logs, stream platform) starting whisk: killed at ... for going over its ... memory limit; the node agent restarts the container a second later and whisk-init prints this as the first line of the new run. A function run in progress when it was killed is lost and fails: its run carries this code, with details.killed_at, on whisk runs and the run's page. The org's owners are notified of the first kill of an app, and then at most once per app per 24 hours.
Fix: Reduce the app's peak memory: stream large downloads and uploads instead of buffering them, keep less in /tmp (hydrate only what a run needs, or read it from storage on demand), and process work in smaller batches. Otherwise move the org to a plan with more memory.
{"error":{"code":"APP_OUT_OF_MEMORY","message":"whisk-init: the previous run was killed with SIGKILL without a stop request, which means it used more than its memory limit of 256 MiB (files in /tmp count as memory).","fix":"Reduce its peak memory (stream large files, keep less in /tmp) or move to a plan with more memory.","docs":"https://skill.whisk.run/errors/APP_OUT_OF_MEMORY","details":{}}}APPCPUSLEEP
Status: - · Surface: container
When: an app on a plan that puts busy apps to sleep (Free) used all of its processor share (half a core on Free) for ten minutes. The platform stops it as the idle sleep does and writes a line into the app's logs (whisk logs, stream platform) starting whisk: put to sleep at ... for using all of its ... processor share. The next request wakes it. A function run in progress is cut off and fails as any interrupted run does. On paid plans a busy app keeps running and the signal is only recorded for the operator.
Fix: Find the loop or heavy job that keeps the app busy, and spread the work out (smaller batches, a scheduled function that does a little each time). Otherwise move the org to a paid plan.
{"error":{"code":"APP_CPU_SLEEP","message":"whisk: put to sleep at 09:30:05 UTC for using all of its 0.5 core processor share for ten minutes (APP_CPU_SLEEP). The next request wakes it.","fix":"Find the loop or heavy job that keeps it busy, spread the work out, or move to a paid plan, where a busy app keeps running.","docs":"https://skill.whisk.run/errors/APP_CPU_SLEEP","details":{}}}APPMEMORYHIGH
Status: - · Surface: container
When: the app's memory, or its peak since it started, reached 85 % of its container's limit (files in /tmp count as memory). whisk-init prints it into the app's log with the numbers, at most every ten minutes. Nothing is stopped yet; above the limit the app is killed at once with APP_OUT_OF_MEMORY.
Fix: Reduce the app's peak memory before it reaches the limit: stream large downloads and uploads instead of buffering them, keep less in /tmp (hydrate only what a run needs, or read it from storage on demand), and process work in smaller batches. Otherwise move the org to a plan with more memory.
{"error":{"code":"APP_MEMORY_HIGH","message":"whisk-init: the app is using 230 MiB (peak 240 MiB, 93%) of its memory limit of 256 MiB, and files in /tmp count as memory.","fix":"Reduce its peak memory (stream large files, keep less in /tmp) or move to a plan with more memory.","docs":"https://skill.whisk.run/errors/APP_MEMORY_HIGH","details":{}}}INIT_CONFIG
Status: - · Surface: container
When: whisk-init has no app command, an invalid option, or a non-positive timeout or grace.
Fix: Correct the node-generated invocation; durations use Go syntax such as 28s.
{"error":{"code":"INIT_CONFIG","message":"whisk-init: invalid startup configuration.","fix":"Correct the command and positive timeout/grace durations.","docs":"https://skill.whisk.run/errors/INIT_CONFIG","details":{}}}INIT_PROCESS
Status: - · Surface: container
When: the runtime cannot enable child subreaping.
Fix: Check runtime support for PRSETCHILD_SUBREAPER; do not start without child reaping.
{"error":{"code":"INIT_PROCESS","message":"whisk-init: cannot enable child reaping.","fix":"Check the container runtime configuration.","docs":"https://skill.whisk.run/errors/INIT_PROCESS","details":{}}}INIT_EXEC
Status: - · Surface: container
When: the image command is missing, cannot be resolved in PATH, or cannot be executed.
Fix: Correct the image command, interpreter, PATH and executable permissions.
{"error":{"code":"INIT_EXEC","message":"whisk-init: cannot execute app.","fix":"Check the image command, PATH and executable permissions.","docs":"https://skill.whisk.run/errors/INIT_EXEC","details":{}}}INIT_SECRETS
Status: - · Surface: container
When: the secrets response is malformed, oversized, has an unsupported version or contains an unrecognised error. Values, paths and upstream error text are not echoed.
Fix: Check node/init protocol compatibility and restart with a fresh token.
{"error":{"code":"INIT_SECRETS","message":"whisk-init: invalid secrets response.","fix":"Check node/init protocol compatibility and issue a fresh start token.","docs":"https://skill.whisk.run/errors/INIT_SECRETS","details":{}}}INITSECRETFILES
Status: - · Surface: container
When: a declared secret file cannot be created privately under /run/secrets.
Fix: Provide an app-owned 0700 tmpfs directory with no existing destination files.
{"error":{"code":"INIT_SECRET_FILES","message":"whisk-init: cannot write private secret files.","fix":"Check ownership, permissions and available space under /run/secrets.","docs":"https://skill.whisk.run/errors/INIT_SECRET_FILES","details":{}}}INITNOTOKEN
Status: - · Surface: container
When: whisk-init found no container token at start. A platform fault; the container is restarted with a fresh token.
Fix: Nothing. If it repeats in the log, the operator has been paged.
{"error":{"code":"INIT_NO_TOKEN","message":"whisk-init: no container token was provided; refusing to start.","fix":"No action; the platform restarts the container with a new token.","docs":"https://skill.whisk.run/errors/INIT_NO_TOKEN","details":{}}}TOKEN_INVALID
Status: - · Surface: container
When: the container's secrets token was not recognised by the node, for example after it expired during a very slow image start.
Fix: Nothing; the container restarts with a fresh token. If the app's image takes minutes to start, reduce its start time.
{"error":{"code":"TOKEN_INVALID","message":"whisk-init: the secrets token was not accepted.","fix":"No action; the platform restarts the container with a new token.","docs":"https://skill.whisk.run/errors/TOKEN_INVALID","details":{}}}TOKENALREADYUSED
Status: - · Surface: container
When: a second secrets fetch was attempted with a container token that was already used. This is either a restart race or something inside the container trying to fetch secrets again, which is reported to the operator as a security signal.
Fix: If the app itself connects to /run/whisk/secrets.sock, stop: secrets are already in the environment. Otherwise no action.
{"error":{"code":"TOKEN_ALREADY_USED","message":"whisk-init: secrets token already used; refusing to start.","fix":"Read secrets from the environment. Nothing in the app should connect to the secrets socket.","docs":"https://skill.whisk.run/errors/TOKEN_ALREADY_USED","details":{}}}UPSTREAM_UNAVAILABLE
Status: - · Surface: container
When: the node could not reach the control plane to fetch secrets within 30 seconds. whisk-init exits so the platform retries.
Fix: Nothing; the container is restarted automatically. Persistent occurrences page the operator.
{"error":{"code":"UPSTREAM_UNAVAILABLE","message":"whisk-init: platform unavailable, exiting so the platform can retry.","fix":"No action; the container restarts automatically.","docs":"https://skill.whisk.run/errors/UPSTREAM_UNAVAILABLE","details":{}}}DISK_CRITICAL
Status: - · Surface: deploy, node
When: the node's disk is above 90% and the node agent refused to start a container or pull an image until space is freed. The deploy that hit it is failed with this code; the previous deploy stays live.
Fix: Nothing on the tenant's side; the operator is paged. Retry the deploy once the node reports space again.
{"error":{"code":"DISK_CRITICAL","message":"The node is out of disk and refused to start job-tracker.","fix":"Retry in a few minutes; the operator has been paged.","docs":"https://skill.whisk.run/errors/DISK_CRITICAL","details":{"node":"node1","disk_used_percent":93}}}POSTGRES_DOWN
Status: - · Surface: deploy, node
When: the node's Postgres cluster did not answer, so a container start, a migration or a database creation was refused. Running containers are untouched.
Fix: Nothing on the tenant's side; the operator is paged. Retry the deploy once the node reports Postgres up.
{"error":{"code":"POSTGRES_DOWN","message":"The database cluster on node1 is not answering, so job-tracker was not started.","fix":"Retry in a few minutes; the operator has been paged.","docs":"https://skill.whisk.run/errors/POSTGRES_DOWN","details":{"node":"node1"}}}IMAGENOCOMMAND
Status: - · Surface: deploy, node
When: the built image has neither an ENTRYPOINT nor a CMD, so there is nothing for whisk-init to run.
Fix: Add a CMD (or ENTRYPOINT) to the Dockerfile that starts the server on PORT, or remove the Dockerfile so Railpack builds one.
{"error":{"code":"IMAGE_NO_COMMAND","message":"The image for job-tracker defines no command to run.","fix":"Add a CMD line, such as CMD node dist/index.js or your server start command, to the Dockerfile and deploy again.","docs":"https://skill.whisk.run/errors/IMAGE_NO_COMMAND","details":{"image":"01J.../01J...@sha256:..."}}}CONTAINERSTARTFAILED
Status: - · Surface: deploy, node
When: Docker or the sandbox runtime refused to start the container. The node's diagnosis is in message; the container's first log lines, when any, are in details.
Fix: Read the message and the log lines. A missing file or a non-executable command is the app's to fix; a runtime error is the operator's.
{"error":{"code":"CONTAINER_START_FAILED","message":"runsc could not start job-tracker: exec /app/start: permission denied.","fix":"Make the start command executable in the image (chmod +x in the Dockerfile) and deploy again.","docs":"https://skill.whisk.run/errors/CONTAINER_START_FAILED","details":{"log_tail":["exec /app/start: permission denied"]}}}CONTAINERNOTFOUND
Status: - · Surface: node
When: the control plane referred to a container the node no longer has, for example after a node rebuild pruned it.
Fix: The control plane redeploys the last live build; retry the operation once the deploy is live.
{"error":{"code":"CONTAINER_NOT_FOUND","message":"The container for job-tracker no longer exists on node1.","fix":"Wait for the redeploy, then retry.","docs":"https://skill.whisk.run/errors/CONTAINER_NOT_FOUND","details":{"container_id":"3f2a..."}}}TOKEN_MALFORMED
Status: - · Surface: node, container
When: a container presented something on the secrets socket that is not a 32-byte base64url token, so the node answered without looking anything up.
Fix: Nothing to do in the app: whisk-init writes the token. If it recurs, the token file was altered inside the container; report it.
{"error":{"code":"TOKEN_MALFORMED","message":"The token presented on the secrets socket was not a Whisk token.","fix":"Restart the container; if it recurs, report it to Whisk.","docs":"https://skill.whisk.run/errors/TOKEN_MALFORMED","details":{}}}WAKE_TIMEOUT
Status: 504 · Surface: edge, node
When: a sleeping app was asked to wake and its container did not reach a healthy state within the wake timeout (60 seconds). The edge shows the platform's timeout page; the node reports the wake.
Fix: Retry in a moment. An app that regularly takes longer than a few seconds to answer its health route should start faster (CONTRACT.md §2, rule 9) or be always_on.
{"error":{"code":"WAKE_TIMEOUT","message":"Starting job-tracker took too long.","fix":"Retry in a moment. If it keeps timing out, make the app start faster or set always_on in whisk.yaml.","docs":"https://skill.whisk.run/errors/WAKE_TIMEOUT","details":{"app":"job-tracker","retry_after":5}}}WAKE_FAILED
Status: 500 · Surface: edge, node
When: a sleeping app was asked to wake and its container could not be started at all (the image is gone from the node, the container would not create, or its network or secrets could not be prepared), as opposed to starting and not becoming healthy (WAKE_TIMEOUT). The edge shows the platform's error page; message carries the node's own words.
Fix: Run whisk status and whisk logs; if the app stays unreachable, run whisk deploy again, which places a fresh container. The operator sees the node's cause.
{"error":{"code":"WAKE_FAILED","message":"The container for job-tracker could not be started: image not present on the node.","fix":"Run whisk deploy if the app stays unreachable.","docs":"https://skill.whisk.run/errors/WAKE_FAILED","details":{}}}FORBIDDEN
Status: 403 · Surface: node
When: something other than the edge on the same node, or other than the platform's mesh, called one of the node agent's own endpoints (the wake call, the metrics, a test endpoint). An app or an agent never meets it: the node agent listens only inside the platform.
Fix: Nothing to do in an app. Call the platform through the API or the app's hostname; the node agent's endpoints are for the edge and the control plane only.
{"error":{"code":"FORBIDDEN","message":"Only the edge on this node may call the node agent.","fix":"Call the platform through the API or the app's hostname; the node agent answers only the edge and the mesh.","docs":"https://skill.whisk.run/errors/FORBIDDEN","details":{}}}FREECAPACITYBUSY
Status: 503 · Surface: edge, node, deploy
When: an app on the Free plan had to start (a wake, or a deploy's new container) and the free apps on its node already use all the memory set aside for them. To make room the node puts to sleep the free apps used least recently, but none that had a request in the last minute; this answer means every one of them is busy right now. Nothing is wrong with the app. A browser sees the app's starting page, which tries again by itself every few seconds; any other client gets this body with Retry-After. A deploy that meets it fails with this code, and the live deploy keeps serving. Apps on a paid plan never wait for this.
Fix: Retry in a few seconds, or move the app to a paid plan so it never waits.
{"error":{"code":"FREE_CAPACITY_BUSY","message":"Free apps are busy right now, so job-tracker has to wait a few seconds to start.","fix":"Retry in a few seconds, or move the app to a paid plan so it never waits.","docs":"https://skill.whisk.run/errors/FREE_CAPACITY_BUSY","details":{"app":"job-tracker","retry_after":5}}}INVALID_ARGUMENT
Status: - · Surface: node
When: the control plane sent the node a request it could not act on: a missing request_id, an empty image, an app id that is not a ULID, a malformed restore target. This is a platform bug, never a tenant's.
Fix: Nothing a tenant sends causes this. If it reaches you, report it with whisk feedback --kind bug --code INVALID_ARGUMENT, naming the request id or the deploy id it came with.
{"error":{"code":"INVALID_ARGUMENT","message":"StartContainer was called without an image.","fix":"Report it with whisk feedback --kind bug --code INVALID_ARGUMENT and the request id.","docs":"https://skill.whisk.run/errors/INVALID_ARGUMENT","details":{"field":"image"}}}CONTROLPLANESUPERSEDED
Status: - · Surface: node
When: a control plane copy that is no longer live sent this command, so the node refused it. Every command to a node carries the epoch of the copy that sent it (NODE-AGENT.md §A13); the node refuses one older than the highest epoch it has seen. A tenant never causes this.
Fix: Nothing to do: the live control plane carries the work out. If it persists, check which copy is live with whiskd live.
{"error":{"code":"CONTROL_PLANE_SUPERSEDED","message":"A control plane copy that is no longer live sent this command, so node1 refused it.","fix":"Nothing to do: the live control plane carries the work out. If it persists, check which copy is live with whiskd live.","docs":"https://skill.whisk.run/errors/CONTROL_PLANE_SUPERSEDED","details":{"node":"node1","epoch":4,"highest_seen":5}}}DATABASE_FAILED
Status: - · Surface: deploy, node
When: the node could not prepare, rotate or drop an app's database, role or cache. On a deploy it arrives as PLATFORM_DEPLOY_FAILED with this code as details.step_code; the message names the node and the part of it that failed, and details carries step (database or cache), node, cause_code (the node's own code: POLICY_APPLY_FAILED for its network rules, DOCKER_FAILED, POSTGRES_DOWN, DISK_CRITICAL, or DATABASE_FAILED for Postgres itself), cause (the node's own words) and fault: platform. No password is ever included.
Fix: Nothing in the app causes this, and the operator has been paged with the cause. Run whisk deploy again later; if it fails the same way, report it with whisk feedback --kind bug --code DATABASE_FAILED, naming the deploy id and details.cause.
{"error":{"code":"DATABASE_FAILED","message":"The app's database could not be prepared on node node1: the node's network rules would not load (nft: exit status 1: /dev/stdin:14:40-58: Error: Could not process rule: Invalid argument).","fix":"Nothing in the app causes this, and the operator has been paged with the cause. Run whisk deploy again later; if it fails the same way, report the deploy id with details.cause.","docs":"https://skill.whisk.run/errors/DATABASE_FAILED","details":{"step":"database","node":"node1","cause_code":"POLICY_APPLY_FAILED","cause":"nft: exit status 1: /dev/stdin:14:40-58: Error: Could not process rule: Invalid argument","fault":"platform"}}}DATABASECREDENTIALSREJECTED
Status: 503 · Surface: api, deploy
When: an environment's database refused the password Whisk holds for it. The password is the platform's, so nothing in the app causes this. From whisk db query and whisk db schema it is the answer itself, with details.node and fault: platform; on a deploy it is the details.cause_code of a PLATFORM_DEPLOY_FAILED whose step code is MIGRATE_FAILED. Every deploy checks the password before it migrates and puts it back in step when it is refused.
Fix: Deploy the app again (whisk deploy); the deploy puts the password back in step. If the answer comes back after that, send it with whisk feedback.
{"error":{"code":"DATABASE_CREDENTIALS_REJECTED","message":"The production database refused the password Whisk holds for this environment. This is on Whisk's side, not the app's.","fix":"Deploy the app again: a deploy puts the password back in step. If this answer comes back after that, send it with whisk feedback.","docs":"https://skill.whisk.run/errors/DATABASE_CREDENTIALS_REJECTED","details":{"node":"node1","fault":"platform"}}}RESTORE_FAILED
Status: - · Surface: deploy, node
When: a point-in-time restore did not complete: the base backup could not be fetched, the temporary instance did not promote, or the dump into the target failed. The temporary instance is stopped and removed and a half-restored database is dropped, so nothing is left behind.
Fix: Read the step and the instance's log tail in details. Retry the restore; the live database is untouched. If it fails the same way, report it with whisk feedback --kind bug --code RESTORE_FAILED and the restore id.
{"error":{"code":"RESTORE_FAILED","message":"The restore of job-tracker to 2026-09-07T14:13:00Z failed while promoting the temporary instance.","fix":"Retry the restore. If it fails the same way, report it with whisk feedback --kind bug --code RESTORE_FAILED and the restore id; the live database is untouched.","docs":"https://skill.whisk.run/errors/RESTORE_FAILED","details":{"step":"promote","log_tail":["FATAL: recovery target not reached"]}}}NODESTATEFAILED
Status: - · Surface: node
When: the node could not write to its own working directory (WHISK_STATE), so it could not keep the file it was handed while scanning an upload, converting a video or resizing an image: the disk is full or read-only, or the directory's permissions are wrong. The upload itself is untouched in the store.
Fix: Nothing on the tenant's side; the operator reads the node log. Retry in a few minutes.
{"error":{"code":"NODE_STATE_FAILED","message":"The node could not write to its working directory, so team-photo.jpg was not resized.","fix":"Retry in a few minutes; the operator has been notified.","docs":"https://skill.whisk.run/errors/NODE_STATE_FAILED","details":{"node":"node1"}}}DOCKER_FAILED
Status: - · Surface: deploy, node
When: the Docker Engine returned an error the node could not classify further (network creation, inspect, stop, remove). The daemon's message is in message.
Fix: The operator reads the node log. Retry the deploy once fixed.
{"error":{"code":"DOCKER_FAILED","message":"Docker refused to create the network for job-tracker.","fix":"Retry in a few minutes; the operator has been notified.","docs":"https://skill.whisk.run/errors/DOCKER_FAILED","details":{"operation":"network create"}}}SEARCHKEYINVALID
Status: 400 · Surface: api
When: the operator connected Google Search Console to the weekly numbers (PUT /v1/operator/numbers/search) with something that is not a service account's JSON key: not JSON, a type other than service_account, no client_email, or a private_key that is not an RSA key in PEM; or with no key while nothing is connected. The import's own search.error carries it too when the stored key can no longer be opened.
Fix: In Google Cloud, open IAM and admin, Service accounts, choose the account, then Keys, Add key, Create new key, JSON, and paste the whole file that downloads.
{"error":{"code":"SEARCH_KEY_INVALID","message":"That is not a service account's JSON key: its \"type\" is not \"service_account\".","fix":"In Google Cloud, open IAM and admin, Service accounts, choose the account, then Keys, Add key, Create new key, JSON, and paste the whole file that downloads.","docs":"https://skill.whisk.run/errors/SEARCH_KEY_INVALID"}}SEARCHKEYREJECTED
Status: - · Surface: api
When: Google refused to sign the service account in with the key Search Console was connected with: the key or the account was deleted or disabled. It is the weekly numbers' search.error; the import is tried again in 30 minutes.
Fix: In Google Cloud, create a new JSON key for the service account and connect again with it on the weekly numbers page.
{"error":{"code":"SEARCH_KEY_REJECTED","message":"Google refused the service account's key: Invalid JWT Signature.","fix":"The key or the service account numbers@whisk-numbers.iam.gserviceaccount.com may have been deleted or disabled. In Google Cloud, create a new JSON key for the service account and paste it here.","docs":"https://skill.whisk.run/errors/SEARCH_KEY_REJECTED"}}SEARCHACCESSDENIED
Status: - · Surface: api
When: Google signed the service account in but will not let it read the Search Console property: the account's email is not a user on the property, or the property is named differently (a domain property is sc-domain:<domain>, a URL-prefix property its address with a trailing slash). It is the weekly numbers' search.error; the import is tried again in 30 minutes.
Fix: In Search Console, open the property, then Settings, Users and permissions, Add user, and add the service account's email (Restricted is enough). If the property is named differently, connect again with its exact name.
{"error":{"code":"SEARCH_ACCESS_DENIED","message":"Google says numbers@whisk-numbers.iam.gserviceaccount.com cannot read the Search Console property sc-domain:whisk.run: User does not have sufficient permission for site 'sc-domain:whisk.run'.","fix":"In Search Console, open the property sc-domain:whisk.run, then Settings, Users and permissions, Add user, and add numbers@whisk-numbers.iam.gserviceaccount.com (Restricted is enough). If the property is named differently, connect again with its exact name.","docs":"https://skill.whisk.run/errors/SEARCH_ACCESS_DENIED"}}SEARCHAPIDISABLED
Status: - · Surface: api
When: the Google Search Console API is not turned on in the Google Cloud project the service account belongs to. It is the weekly numbers' search.error; the import is tried again in 30 minutes.
Fix: In Google Cloud, open APIs and services, Library, find Google Search Console API and choose Enable, then wait a few minutes.
{"error":{"code":"SEARCH_API_DISABLED","message":"The Google Search Console API is not turned on for the service account's project.","fix":"In Google Cloud, open APIs and services, Library, find Google Search Console API and choose Enable, then wait a few minutes; Whisk tries again in 30 minutes.","docs":"https://skill.whisk.run/errors/SEARCH_API_DISABLED"}}SEARCH_UNAVAILABLE
Status: - · Surface: api
When: Google did not answer the import, answered with an error on its side (a 5xx or too many requests), or answered something the import could not read. It is the weekly numbers' search.error; what was imported before stays.
Fix: Nothing to do: the import is tried again in 30 minutes. If it keeps failing, check https://status.cloud.google.com.
{"error":{"code":"SEARCH_UNAVAILABLE","message":"Google answered 503: Backend Error.","fix":"Nothing to do now: Whisk tries again in 30 minutes. If it keeps failing, check https://status.cloud.google.com.","docs":"https://skill.whisk.run/errors/SEARCH_UNAVAILABLE"}}