Every error and log signature Embarko has really returned, with its cause and fix.
Each entry has three parts:
| Part | What it is |
|---|---|
| Match | The exact text to look for in a response body or in logs |
| Cause | Why it happens |
| Fix | What to do, and whether to retry |
This page covers deploying (POST /apps) and everything you do to an app after: env vars, rollback, custom domains, analytics, rename and delete. Memory is not on the list because nobody can change it. The call-by-call contract is in api.md. Constraints to design around before you hit them are in limitations-and-recommendations.md.
| Status | Code or match | Retry? | Entry |
|---|---|---|---|
| none | Connection refused, DNS failure, hang | No | The call never completes |
| 401 | unauthorized | No | 401 unauthorized |
| 409 | app_name_taken | No, change the name | 409 app_name_taken |
| 409 | not_supported_for_static | No | Env var write refused on a static site |
| 422 | unsupported_storage_pattern | No, fix the code | 422 unsupported_storage_pattern |
| 422 | no_manifest_at_root | No, repackage | 422 no_manifest_at_root |
| 422 | app_nested_in_subdirectory | No, deploy the subfolder | 422 app_nested_in_subdirectory |
| 422 | rollback_image_unavailable | No, pick a newer target | Rollback target image unavailable |
| 422 | Other rollback error | No, pick another target | Rollback returns 422 for a different reason |
| 502 | app_name_check_failed | Yes, as is | 502 app_name_check_failed |
| 502 | Agent customer-query endpoint | Yes | 502 from the agent customer-query endpoint |
| 400 | X-Agent-Name header is required | No, add the header | 400 X-Agent-Name |
| 404 | {"error":"Not found"} on status or logs | No, fix the token | 404 on status/logs |
| 202 | "status":"building" or "accepted":true | Poll, don't resend | Async operations |
| 524 | A timeout occurred | Check upload first | 524 timeout |
Check this first. Any 4xx or 5xx means the platform answered. No status at all means it did not.
Match: no HTTP status at all from POST /apps. A connection error, a DNS failure, a proxy rejection, or a request that hangs and dies.
Cause: your environment is usually network-sandboxed with a domain allowlist that leaves out ship.embarko.ai. This is common in hosted AI coding tools. The request is blocked before it leaves, so nothing was submitted: no deploy, no status to poll, no logs.
Fix: don't retry. The same call fails the same way.
Full procedure: agent playbook. Do not fall back to an anonymous deploy. It uses the same host, hits the same block, and would hand the user a temporary app they didn't ask for.
"window.storage" in the 422 response detail.window.storage. That API exists only inside the Claude Artifacts sandbox. The deploy is rejected before any build runs.filesDetected. It is an array of the exact source paths that make the call, so you don't need to search.better-sqlite3) writing under DATA_DIR. See Only DATA_DIR survives a redeploy.{
"error": "Unsupported storage pattern detected",
"detail": "This app calls window.storage, an API specific to Claude Artifacts' sandbox...",
"filesDetected": ["src/app.jsx"],
"docs": "https://embarko.ai/docs/docs.md#storage-requirements"
}
The docs URL is what the platform returns today. /docs/docs.md now redirects to the API contract and that fragment no longer exists. Use the database capability instead. The platform should be updated to point there.
{"error":"Unauthorized","code":"unauthorized"}Authorization header was sent, but the token is invalid or revoked. A missing header means an anonymous deploy, which is fine. A broken header is never treated as anonymous."already in use by a different company"X-App-Name is also the live subdomain, so it must be unique across the whole platform, not just your account.X-App-Name. Or, if it is your orphan app, claim it with a real token before its 24h expiry.Match: "code":"no_manifest_at_root" in the 422 response. Before this check existed, the same problem showed as a generic build failure with a non-zero or unusual exit code and no real install or compile output before it.
Cause: the app files sit in a subfolder instead of at the top of the archive. The build only looks at the archive root for a manifest (package.json, requirements.txt, pyproject.toml, go.mod, Gemfile, composer.json, Cargo.toml) or index.html. One single wrapping folder (for example a zip that already held one top-level folder) is fixed automatically. Anything more ambiguous returns this error instead of guessing.
Fix:
detail and foundNestedAt fields. They say exactly where a manifest was found, if anywhere.The embarko-deploy skill's deploy.sh also checks this locally. It fails before uploading and names the inner folder to point it at.
"code":"app_nested_in_subdirectory" in the 422 response.package.json at the archive root, but it declares no dependencies of its own and hands the build to a subfolder. Examples: "build": "npm --prefix frontend run build", a cd frontend && … script, or a workspaces entry. This is the usual frontend/backend repo, deployed from the repo root by mistake. Dependencies are only installed at the archive root, so the subfolder's node_modules never exists and the build dies minutes in on a missing command (exit code 127).deployThisInstead field names it (for example frontend). Upload that folder directly, or run ./scripts/deploy.sh ./frontend <app-name>. One deploy is one app. A repo with a frontend and a backend needs two deploys under two app names."code":"app_name_check_failed"GET .../logs. With no runtime logs, it returns the build output instead. The response carries source: "build" and a note saying so. It names the command that failed. Read it before guessing.Match: exit code: 127, usually followed by a line like open /var/lib/docker/tmp/docker-import-… : no such file or directory.
Cause: exit 127 means "command not found" in the build image. npm ran fine. The tool the build script called is missing. Two common reasons:
dependencies or devDependencies, orpackage.json hands the build to a subfolder whose dependencies were never installed. That shape is now rejected before the build. See 422 app_nested_in_subdirectory.The docker-import line is only a side effect. No image was built, so importing it failed too. It is not the real error.
Fix:
GET .../logs. It names the missing command.Match: "OOM Killed", Exit Code: 137
Cause: the app used more than its 256MB of memory. Common reasons: an embedded database the platform didn't auto-detect, or a dev server (for example a "dev" CLI) shipped as the production start command.
Fix: shrink the app. You cannot raise the limit. There is no agent call for memory and the dashboard control is gone.
If memory ever becomes adjustable again, setting it only saves the value. See Memory cannot be changed today.
"Not Restarting", "Exceeded allowed attempts"GET .../logs, find the cause above this line, fix it, and redeploy. A redeploy resets the attempt counter."pull access denied"CompileError from WebAssembly.compile or instantiate, or a CSP violation naming script-src. The page loads (200) but is blank.package.json) are served by Railpack's built-in static file server. Its default Content-Security-Policy had no 'wasm-unsafe-eval' in script-src. Before the fix, the platform set no CSP at the edge, so the browser saw whatever the app's container set.'wasm-unsafe-eval'). It overrides anything the app's container sets.
curl -sI https://<app>.embarko.app/ | grep -i content-security-policy.wasm-unsafe-eval is missing, the app hasn't been redeployed since the fix. Trigger any redeploy, even a rollback to the current version.{"error":"Not found"} from GET .../status or .../logsAuthorization: Bearer <deploy token> for a claimed app. Leave the header out if the app is still meant to be an orphan.The path is .../customer-query on all three routes (dashboard, public, agent). It was .../feature-requests before 2026-09-09. Same data and behavior. type now also accepts "other" alongside "feature" and "feedback".
"X-Agent-Name header is required"POST <ship host>/apps/:appName/customer-query (the agent path) without the X-Agent-Name header.X-Agent-Name: <your agent's name>. It is free text. Any value that identifies the calling agent works.POST .../apps/:appName/customer-querytype must be one of platform_bug, missing_capability, docs_issue, unexpected_behavior, other, feature or feedback.message must be 1 to 5000 characters."DNS does not yet point at the platform"POST .../domains response exactly..../verify after propagation. Safe to retry many times.POST .../domains/<domain>/verify keeps failing, or the dashboard shows "Not resolving yet", even though the CNAME or A record matches exactly..../verify. It should pass within a minute or two. There is no propagation delay here, only the mode change.CNAME to a dedicated platform hostname that is never proxied. An apex domain (yourdomain.com, no subdomain) can't use CNAME under standard DNS rules. It gets an A record pointing straight at the platform's origin server IP instead. So an apex domain talks to the origin directly, and any CDN or DDoS protection you'd get from proxying (Cloudflare and others) doesn't apply.app.yourdomain.com) instead of an apex domain if you want to keep your own CDN or proxy in front of this app./.well-known/acme-challenge/*. If your own firewall or proxy in front of this domain blocks or redirects port 80, verification and certificate issuance never finish.Match: none. This is by design.
Cause: a deploy with no Authorization header creates an unclaimed ("orphan") project. It is torn down 24 hours after creation unless claimed. The running app, its routing and its image are all removed.
Fix: redeploy the same X-App-Name with a valid token before it expires. That claims it for your company, cancels the expiry, and from then on it works like any other project.
The warnings are app.expiresAt and the claim steps in the status reply's next once the app is live, plus an in-app banner (see below). No reminder email is sent before expiry.
text/html responses. It works with any language or framework. A JSON-only API has no HTML page to inject into. The status reply's app.expiresAt and next are still the official notice. They just never reach a browser.{"success":true,"status":"building"} (or "publishing" for a very large static site), HTTP 202, from POST /apps. A static site that published in time answers 200 with "status":"live" and needs no polling.links.status from the response until status is "live" or "failed". Never treat 202 as done.{"code":"not_supported_for_static"}, HTTP 409, from PUT .../env-vars, PUT .../env-vars/<key> or POST .../env-vars/applyapp.kind: "static"). It is served straight from its files and nothing runs on the server, so env vars have nowhere to go.config.js), or deploy it as a server app under a new app name.{"status":"not_available"} from GET .../logs, with app.kind: "static"{"accepted":true,"deploymentId":"...","statusUrl":"..."}, HTTP 202, from POST .../rollbackstatusUrl (GET .../deployments/:deploymentId) until status is no longer "in_progress".524, "A timeout occurred""code":"rollback_image_unavailable", from POST /api/apps/<app-name>/rollback with {"deploymentId": "..."} (the agent path, deploy token) or the dashboard's Rollback button. The message starts with "Cannot roll back: the image is no longer available". Older responses said "is no longer available on this host". Branch on code, which is stable, not on either wording.limitations-and-recommendations.md). The response's retainCount field says exactly how many. The target version's image has already been removed.retainCount deployments instead (see GET .../deployments). Or redeploy the old source fresh if you still have it. The exact number is in the error response, so you don't need to look it up.POST .../rollback, and error does NOT start with "Cannot roll back: the image is no longer available"status !== "success") or has no recorded image tag.GET .../deployments. Only successful deployments with an image can be rollback targets.Match: none. Expected behavior, not an error.
Cause: env vars are written into the app's configuration at deploy time. They are not pushed live to a running container.
Fix: after writing an env var, do one of these so the app sees the new values:
POST /api/apps/<app-name>/env-vars/apply. This is faster because it skips the build.POST /apps, even with unchanged source).Exact calls: environment variables contract.
Limits that apply to every app, all the time, are not failures. They are listed with a recommendation for each in limitations-and-recommendations.md.