Errors
The Shardflux error envelope and every error code and details.reason the API and the cell gateway return, with HTTP status, meaning and what to do.
The error envelope
Every non-2xx response from the application API and from the cell gateway has the same body:
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Try again later.",
"request_id": "req-9b0e1d2c3f4a5b6c7d8e9f00",
"retryable": true,
"details": { "retry_after_seconds": 42 }
}
}| Field | Meaning |
|---|---|
code |
A closed set of snake_case codes (below). Branch on code and details.reason, never on message. |
message |
Safe, human-readable English. Show it when you have nothing better for a code. |
request_id |
The same value as the X-Request-Id response header. Include it when you contact support. |
retryable |
Whether the same request may succeed later. |
operation_id |
Optional. The lifecycle operation that caused the error; poll it with GET /v1/operations/{id}. |
details |
Optional. Per-code details, most often reason, plus fields such as field, names or limit. |
Treat a code or reason you do not know as a generic error: show message and use retryable. New reasons can appear
at any time within /v1.
A few 502, 503 or 504 responses from the network edge carry a different body. Treat them as retryable. The SDKs raise
ShardfluxProtocolError for them.
Retrying
| Situation | Retry? |
|---|---|
GET or HEAD, and retryable: true or status 429, 502, 503 or 504 |
Yes, with exponential backoff, honouring Retry-After. |
A mutation sent with an Idempotency-Key, after a retryable error or a network failure |
Yes, with the same key and the same body. |
A mutation without an Idempotency-Key |
Not automatically: the request may have taken effect. |
retryable: false |
No. Change the request, the plan or the credentials first. |
409 with an operation_id (operation_in_progress, workspace_not_running, workspace_busy) |
Wait for the operation, then retry. |
Cell 409 stale_epoch |
Once, after getting a new tool token. |
429 quota_exceeded, and cell 503 service_unavailable with details.reason: host_capacity |
Yes, any request, after Retry-After: it was refused before anything ran. |
| An execution of a file-first workspace, after a network failure or a retryable error | Yes, with the same execution_id and body: the command runs at most once. |
409 conflict with revision_mismatch or tree_revision_mismatch |
Not as it is. Read the current state, then send a new request. |
The TypeScript and Python
SDKs apply these rules for you. They send a request refused with 429 quota_exceeded or 503 host_capacity again
whatever its method, after the full Retry-After, for up to admissionTimeoutMs / admission_timeout (2 minutes
by default; TypeScript 0.22.0+, Python 0.18.0+).
Application API codes
Codes returned by https://api.shardflux.dev/v1:
| Code | HTTP | Retryable | Meaning | What to do |
|---|---|---|---|---|
bad_request |
400 | no | Malformed JSON, or Content-Type: application/json with an empty body. |
Fix the request. Omit the header on a request without a body. |
token_invalid |
400 | no | An emailed link (email verification, password reset, email change) or a 6-digit email code is invalid, expired or already used; 5 wrong attempts end a code. | Request a new link, or a new code (shard verify). |
unauthenticated |
401 | no | The Authorization header is missing or not Bearer, or the API key or a person's CLI session is malformed, unknown, revoked or expired. |
Check SHARDFLUX_API_KEY; create a new key in the console if it was revoked. For a session, sign in again (shard auth login). |
invalid_credentials |
401 | no | Sign-in (/v1/auth) with a wrong email or password, or a wrong two-factor or recovery code. The same answer for an account that does not exist. |
Check the credentials. Repeated wrong codes lock the second factor (429, mfa_locked). |
entitlement_required |
402 | no | Your plan does not allow this start, build or volume (see reasons). | Change the plan or settle the payment. |
card_required |
402 | no | Compute runs on organizations with a card on file, and this one has none yet (see card_required). details has reason, organization_id, plan and add_card_url. |
The organization's creator saves a card at details.add_card_url (nothing is charged); then start again. Do not retry in a loop. |
allowance_exhausted |
402 | no | A compute allowance of your plan is used up and overage is off or paused, or overage reached its spend cap (see reasons). details has reason, allowance, meter, unit, used, included, resets_at, plan, exhausted and spend_cap. |
Act on details.reason. Do not retry in a loop. |
quota_exceeded |
403 | no | A plan limit was reached. details.limit names it, with limit_value and current (see limits). |
Act on details.limit, or upgrade. |
quota_exceeded |
429 | yes | An open, resume or fork would start one more workspace than the plan's workspaces working at once, and none stopped working within 10 seconds (see 429 quota_exceeded). Nothing started. | Retry after Retry-After; the SDKs do (TypeScript 0.22.0+, Python 0.18.0+). Or upgrade. |
forbidden |
403 | no | The key may not do this: a tool it lacks (details.denied_tools, permitted_tools), an owner or admin action, an action only a person can take ("API keys cannot perform this action."), or a reason below. |
Use a key with the permission, sign in as a person (shard auth login), or ask an owner or admin. |
mfa_required |
403 | no | A person's session is still waiting for its second factor. | Complete the sign-in: POST /v1/auth/mfa/challenge, or shard auth mfa --code <code>. |
email_unverified |
403 | no | A person's session before the email address is verified. A provisional account (an --email signup) gets it only on Checkout, the billing portal, invoices, spend policy, invitations and credential changes. |
Verify it with a 6-digit code (shard verify, or POST /v1/auth/verify-email/code) or the emailed link (shard auth verify-email '<link>'). |
step_up_required |
403 | no | A sensitive action (exports, deletions, email and two-factor changes) without a password check in the last 10 minutes. | Step up (POST /v1/auth/step-up, or shard auth step-up), then retry. |
not_found |
404 | no | The resource does not exist, or it is outside the key's project. The two are not distinguished. An open with a template that does not resolve names it in details.template. |
Check the id, key or slug; GET /v1/templates (shard templates list) lists the templates you can open. |
conflict |
409 | depends | The resource is in a state that refuses the request; details.reason says which (see reasons). |
Act on the reason. |
payload_too_large |
413 | no | The request body is too large. | Send less. |
unsupported_media_type |
415 | no | The body is not JSON. | Send Content-Type: application/json. |
validation_failed |
422 | no | The request is invalid: details.issues (schema checks: path and message) or details.field and details.reason (see reasons). |
Fix the named field. |
idempotency_mismatch |
422 | no | The Idempotency-Key was already used with a different request within 24 hours. |
Use a new key for a new request. |
rate_limited |
429 | yes | Too many requests. Retry-After gives the seconds to wait, also in details.retry_after_seconds. |
Wait, then retry. |
internal_error |
500 | depends | An unexpected server error. | Follow retryable: retry safe requests only when it is true; otherwise report the request_id. |
dependency_unavailable |
503 | yes | A dependency is temporarily unavailable (a reason may say which). Database connection and query timeouts include Retry-After: 2. |
Retry idempotent calls with backoff, honouring Retry-After. |
capacity_pending |
503 | yes | An operation state (see Operation errors): an open, resume or fork reports it on its operation. A resize returns it as an HTTP error when the workspace's host cannot take the larger disk right now (details.reason: host_disk_insufficient or limit_exceeded, with the operation_id). |
Keep waiting for the operation. Send a resize again later. |
stale_epoch |
409 | yes | Returned by the cell gateway (below). | Get a new tool token. |
authorization_pending |
400 | yes | Browser sign-in (POST /v1/auth/device/token): the person has not approved yet. details.interval gives the seconds between polls. |
Poll again after details.interval seconds. |
slow_down |
400 | yes | Browser sign-in: polled faster than the interval. details.interval (also Retry-After) is the new interval, 5 seconds longer. |
Poll at the new interval. |
access_denied |
400 | no | Browser sign-in: the person denied it on the sign-in page. | Start a new sign-in (shard auth login --browser). |
expired_token |
400 | no | Browser sign-in: the device code is unknown, already used, or was not approved within its 10 minutes. | Start a new sign-in. |
Requests with an API key never receive token_invalid, invalid_credentials, mfa_required, email_unverified,
step_up_required or the browser sign-in codes: they come from /v1/auth and from requests with a person's CLI session (Authorization: Bearer sfu_...; see Authentication). The console's own routes also use
csrf_failed, which /v1 never returns.
Cell gateway codes
Codes returned by a workspace's cell_endpoint (exec, files, terminals, processes, git, browser):
| Code | HTTP | Retryable | Meaning | What to do |
|---|---|---|---|---|
bad_request |
400 | no | Invalid query or path parameters, or malformed JSON or unknown fields. | Fix the request. |
unauthenticated |
401 | no | No tool token, or it is malformed, expired or revoked (details.reason: revoked_watermark, revoked_workspace, revoked_api_key, revoked_api_key_tools). |
Get a new tool token and retry once. |
forbidden |
403 | no | The token is for another workspace or lacks the tool (details.tool); or a named secret is not available (secret_not_available). |
Request a token with the tool; check the key's permissions. |
not_found |
404 | no | The workspace is unknown or deleted, or a file does not exist (on a file-first workspace also any path outside /home/user, outside_tree_root). |
Stop, or fix the path. |
conflict |
409 | depends | stale_endpoint (the workspace moved to another cell), a session_id or Idempotency-Key reused for a different request (operation_id_reused), an interrupted attempt whose outcome is unknown (operation_interrupted), legacy_disk_layout, guest_feature_unavailable, and the reasons of file tools and hosts and file-first workspaces. |
stale_endpoint: read the workspace again and use its new cell_endpoint. Interrupted: reattach to the session instead of re-running. |
stale_epoch |
409 | yes | The token was issued before the workspace's latest suspend, resume or move. | Get a new tool token and reconnect with your last offsets. |
Computer use (/computer, /computer/actions, /computer/stream):
| Code | Status | Retryable | Meaning | What to do |
|---|---|---|---|---|
forbidden |
403 | no | The token lacks the computer tool (details.tool): computer use is off, or the token was issued before it was switched on. |
Switch it on and get a new tool token (the SDKs retry once with a fresh token). |
conflict |
409 | no | computer_use_unavailable: the template version has no desktop. desktop_failed: the desktop could not start (the template's session script). |
Use a template with a desktop; check /etc/shardflux/desktop/session. |
validation_failed |
422 | no | invalid_computer_action: an action is invalid (details.index): an unknown action, a missing or extra parameter, a coordinate outside the screen, or a batch limit. |
Fix the action at details.index. |
dependency_unavailable |
503 | yes | desktop_failed: the desktop did not answer. |
Retry. |
workspace_not_running |
409 | yes | The workspace is suspended or not on a host. The call was not executed. A suspended workspace's file reads are answered from its disk while a host still holds it; offline_unavailable and offline_budget say that such a read could not be. |
Resume it (or open the key), then retry with a new token. |
workspace_busy |
409 | yes | A lifecycle operation holds the workspace (operation_id; details.operation_kind; details.tool_gate: read_only or closed; or details.reason: workspace_fenced), or an execution of a file-first workspace runs (execution_in_progress, details.execution_id). The call was not executed. |
Wait for the operation or the execution, then retry. During read_only, reads still work. |
validation_failed |
422 | no | Invalid parameters (paths, a relative cwd (invalid_cwd), modes, signals, terminal size, a reserved sfstart. session id, env_collision, a resource_hint other than auto, light or heavy), or a patch that does not apply (edit_not_found, edit_ambiguous, edit_not_text, patch_invalid). |
Fix the request. |
payload_too_large |
413 | no | The body, stdin (over 1 MiB) or patch (over 7 MiB) is too large (details.max_bytes). |
Send less. |
range_not_satisfiable |
416 | no | The Range is not a single bytes=start-end, or starts past the end of the file (details.size). |
Request within the size. |
quota_exceeded |
429 | yes | The call would wake this workspace while your organization already has its plan's workspaces working at once, and none stopped working within 5 seconds (see 429 quota_exceeded). Nothing ran. | Retry after Retry-After; the SDKs do (TypeScript 0.14.0+, Python 0.10.0+), for up to 2 minutes from TypeScript 0.22.0 and Python 0.18.0. Or upgrade. |
workspace_disk_full |
507 | no | The workspace filesystem has no available space or inodes, or a filesystem quota was reached. details.used_bytes and limit_bytes report measured filesystem usage when available; available_bytes, used_inodes and limit_inodes identify the exhausted resource. |
Delete files through the files API (files.remove / files_remove) or a cleanup exec, then retry the write. Waiting alone does not free space. |
rate_limited |
429 | yes | A transient workspace concurrency limit was reached, or there were too many secret resolutions. Retry-After gives the seconds to wait. |
Back off and retry idempotent calls. |
dependency_unavailable |
503 | yes | The workspace host or a gateway dependency is temporarily unavailable, or the disk changed during a read of a sleeping workspace (offline_changed). |
Retry with backoff. |
service_unavailable |
503 | yes | The gateway cannot take the request right now. Also: the workspace's host is making room to wake it and the call waited for it as long as it could (host_capacity, see file tools, parking and hosts), a wake failed (wake_failed), or the execution cannot be placed right now (no_execution_host). Nothing was executed. |
Retry after Retry-After, or a second. The SDKs retry host_capacity for any call (TypeScript 0.22.0+, Python 0.18.0+). |
timeout |
504 | yes | The workspace did not answer in time. | Retry idempotent calls. For exec, reattach to the session instead of starting it again. |
internal_error |
500 | no | An unexpected gateway error. | Report the request_id. |
The cell enum also contains capacity_pending; the gateway never returns it.
Reasons
details.reason refines a code. The tables list the reasons a request with an API key can meet; the last
one adds those of a person's session.
402 entitlement_required
| Reason | Meaning | What to do |
|---|---|---|
stripe_trial_required |
A new account has not completed the $0 Stripe card check for its 7-day signup trial. | Run shard billing trial --wait, complete the card check in Usage & billing, or choose a paid plan. |
trial_identity_required |
Starting the signup trial also needs a verified work domain (at least 30 days old with active MX records) or a linked GitHub account (at least 30 days old). | Link an established GitHub account or verify a work domain in the console, then run shard billing trial --wait, or choose a paid plan ($9/mo Developer and up). |
subscription_required |
The organization's trial or subscription has ended, or no paid plan is active for workspace compute. Retained storage up to 10 GiB is kept. | Choose a paid plan ($9/mo Developer and up) in Usage & billing or with shard billing upgrade <plan>. |
free_organization_limit |
This account already used its signup trial in another organization; additional organizations need a paid plan for compute. | Use that organization during its trial, or choose a paid plan for this one. |
no_plan |
The organization has no active plan. | Choose a plan in the console. |
limits_missing |
The plan lacks a limit the request needs (details.missing). |
Upgrade. |
not_included |
The plan does not include this feature (shared volumes; for the GPU see GPU frame requests). | Upgrade. |
payment_past_due |
A payment is overdue and the grace period ended (details.grace_until). New starts are refused; running workspaces are not affected. |
An owner or billing member updates the payment method. |
unpaid |
The subscription is unpaid. | As above. |
operator_hold |
New starts are on hold for the organization. | Contact support. |
402 card_required
Opening, resuming or forking a workspace, or building a template, in an organization without a
card on file. details.add_card_url is the console's Usage & billing page for that
organization, where its creator saves a card on a Stripe page in setup mode (nothing is charged). From a person's session
the same check is POST /v1/organizations/{id}/billing/card-checks: it returns the Stripe page (url), and
GET .../card-checks/{card_check_id} reports succeeded once the card is saved. GET .../billing/subscription has
card_required: true until then. Running workspaces keep running. A paid plan, or a plan arranged with Shardflux,
counts as a card on file.
{"error":{"code":"card_required","message":"Compute runs on organizations with a card on file. The organization's creator saves one at https://app.shardflux.dev/billing?organization_id=01a1…&add_card=1; nothing is charged.","request_id":"req_…","retryable":false,"details":{"reason":"card_required","organization_id":"01a1…","plan":"free","add_card_url":"https://app.shardflux.dev/billing?organization_id=01a1…&add_card=1"}}}402 allowance_exhausted
New opens, resumes and forks are refused, and the organization's running workspaces are suspended with their files, memory and processes kept. Nothing is deleted. See Overage. GPU-hours never refuse a start: used up, they stop only the GPU (GPU frame requests).
| Reason | Meaning | What to do |
|---|---|---|
allowance_used |
The plan's CPU-hours or RAM GiB-hours allowance is used up, and overage is off or not available on the plan. | Upgrade, or wait for resets_at on a recurring plan. An owner or billing member can turn on overage when spend_cap is available. |
overage_paused |
The allowance is used up, and overage is on but paused while a plan payment is past due. | An owner or billing member pays the invoice or updates the payment method. |
spend_cap_reached |
Overage charges reached the spend cap for this billing period. | Wait for resets_at, have an owner or billing member raise the cap (up to the plan price), or upgrade. |
details field |
Meaning |
|---|---|
reason |
One of the reasons above. |
allowance, meter, unit |
The first used-up allowance (cpu_hours or ram_gib_hours), its meter (cpu_seconds or memory_gib_seconds), and the unit of used and included. |
used, included |
This period's usage and the plan's allowance, in unit. |
used_meter_units, included_meter_units |
The same in meter units (CPU-seconds or GiB-seconds). |
exhausted |
Every used-up allowance, each with allowance, meter, used, included and unit. |
resets_at |
The end of the billing period. Recurring allowances and spend caps reset then; for a time-limited plan grant, this is the grant end, not a renewal. |
plan |
The plan key, for example developer. |
spend_cap |
null when overage is not available on the plan. Otherwise cap_minor (the configured cap, null if never set), effective_cap_minor (the cap that applies: cap_minor limited to the plan price, or the plan price when no cap is set), charges_minor (overage charged this period) and currency. Amounts are in minor units (cents). |
A time-limited plan grant counts usage across its whole grant window. Exhausting its CPU-hours or RAM GiB-hours ends the grant; waiting does not renew it, and workspace compute pauses until you choose a paid plan while up to 10 GiB of retained workspace storage is kept. When spend_cap is null, overage is unavailable.
GPU frame requests
A GPU app's control socket (/run/sfgpu/<pid>.sock) answers each frame request with one JSON line. When the platform does not render it, ok is false, status is 402 and retryable is false; the workspace and the app
keep running, and rendering comes back on its own once the cause clears.
{"ok":false,"error":"allowance_exhausted","status":402,"retryable":false,"reason":"allowance_used","message":"..."}error |
reason |
Meaning | What to do |
|---|---|---|---|
entitlement_required |
not_included |
The plan has no GPU: "GPU needs the Startup or Scale plan." plan names the current plan and required_plans is ["startup","scale"]. |
Upgrade to Startup or Scale. |
allowance_exhausted |
allowance_used |
This period's GPU-hours are used up and overage is off. | Wait for the next period, upgrade, or have an owner or billing member turn on overage in the console. |
allowance_exhausted |
overage_paused |
The GPU-hours are used up, and overage is on but paused while a plan payment is past due. | An owner or billing member pays the invoice or updates the payment method. |
allowance_exhausted |
spend_cap_reached |
Overage charges reached the spend cap for this billing period. | Wait for the next period, have an owner or billing member raise the cap (up to the plan price), or upgrade. |
The organization's usage summary shows the same state: gpu.cap_state is gpu_blocked and gpu.blocked_reason is the
reason.
GPU video sessions
Video sessions are counted per workspace (Limits). A create past the workspace's count, or one that finds no session free on its GPU within the admission wait, is refused, and nothing is created:
| API | The create | What it returns | What to do |
|---|---|---|---|
| Vulkan Video | vkCreateVideoSessionKHR |
VK_ERROR_OUT_OF_DEVICE_MEMORY after a wait of up to 2 s; the driver log says how long to wait before trying again |
Destroy a session, try again after that wait, or decode and encode in software. FFmpeg does that on its own. |
| NVDEC | cuvidCreateDecoder, cuvidCreateVideoParser |
CUDA_ERROR_OUT_OF_MEMORY after libnvcuvid.so.1 has retried it for 10 s |
Destroy a decoder or parser, or try again. |
GPU readbacks
A GPU app that waits for GPU results (Blender with SFGPU_READBACK=1, compute results read from mapped memory) or for
the GPU itself (a CUDA call) takes its turn when the GPU is busy. A Vulkan readback that gets no GPU within 10 minutes
ends with VK_ERROR_DEVICE_LOST, and the app's log has a [sfgpu] readback: line with the reason. Start the render
again; the next one waits its turn the same way.
403 quota_exceeded
details.limit |
Meaning |
|---|---|
retained_state |
The organization's Retained state has reached the plan's allowance (limit_value and current in GiB), so opening a new key and forking are refused. Existing workspaces keep running, waking, suspending and resuming. Delete workspaces you no longer need, or upgrade. |
concurrent_template_builds |
Unfinished template builds. Wait for one to finish. |
shared_volumes_max |
Shared volumes of the organization. |
shared_volume_gib_max |
The size of one shared volume. |
volume_attachments_per_workspace |
Volumes attached to one workspace. |
429 rate_limited
Platform request and workspace-credential budgets return rate_limited, retryable: true, and a Retry-After
header with the delay in seconds, also in details.retry_after_seconds. Honor that delay before retrying.
details.reason |
Meaning | What to do |
|---|---|---|
api_request_budget |
The API request budget is busy. | Reduce polling, reuse results, and retry after the indicated delay. |
tool_token_budget |
The workspace credential budget is busy. | Reuse unexpired workspace credentials and retry after the indicated delay. |
details.rate_limit identifies resource, scope (api_key, organization, or workspace), limit,
window_seconds, burst, plan_key, catalog_version, and workspace_units. Keys in the same organization share
its organization budget; rotating keys does not increase it. These budgets are separate from plan usage allowances
and concurrent workspace admission.
429 quota_exceeded
Returned when a request would make one more workspace work than the organization's plan allows at once:
- by the cell gateway, for a call that would wake a parked workspace. The call waits up to 5 seconds for a workspace to stop working first; calls to a workspace that is already working never wait.
- by the API, for an open, resume or fork (
details.limit: concurrent_workspaces). The request waits up to 10 seconds for a workspace to stop working first; reopening a workspace that is already running never waits.
{
"error": {
"code": "quota_exceeded",
"message": "Your plan allows 10 workspaces working at once and none became free within 5 seconds; retry shortly, or upgrade.",
"request_id": "req-9b0e1d2c3f4a5b6c7d8e9f00",
"retryable": true,
"details": { "limit": "concurrent_workspaces", "limit_value": 10, "current": 10, "retry_after_seconds": 2 }
}
}- The request was refused before anything ran or started, so any request is safe to retry after
Retry-After(also indetails.retry_after_seconds): opens, commands, stdin, terminals and file writes included. The SDKs retry it: the cell gateway's since TypeScript 0.14.0 and Python 0.10.0, the API's from TypeScript 0.22.0 and Python 0.18.0. From those versions on, both are retried for up toadmissionTimeoutMs/admission_timeout(2 minutes by default). - The API's refusal also carries
details.plan. - A tool call, a keepalive and a wake hint can get it; a wake hint gets it at once, without the wait.
- A WebSocket attach (exec output, terminal) is refused with the same
429response. From a browser, the connection opens, sends this body as its first text message and closes with code4429, whose close reason is compact JSON withcodeandretryable. The SDKs read both and raise the error with its own code and details (TypeScript 0.22.0+, Python 0.18.0+).
403 forbidden
| Reason | Meaning |
|---|---|
template_dev_mode_role |
Drafts and test instances (their creation, lifecycle calls and tool tokens) need an owner, an admin or an API key with a tool permission. |
secret_not_available |
An exec or terminal start names a secret (details.names) this workspace may not use; nothing was started. Also returned for starts while a bound secret's status is not_allowed. |
edge_refused |
The edge firewall refused the request before it reached the API; request_id is edge. File paths on the file routes are data and pass. Send file contents and command text in the request body. |
404 not_found
| Reason | Meaning |
|---|---|
draft_not_found |
The template has no draft. |
version_not_found |
No such template version. |
path_not_found |
The path is not in the version's file tree (details.path). |
checkpoint_not_found |
No such checkpoint of the workspace. |
draft_state_not_found |
No such draft state. |
package_not_found |
No package with that name in the ecosystem. |
log_not_available |
The build has no full log yet; use the log tail on the build. |
log_expired |
The build's full log is past its retention. |
409 conflict
| Reason | Retryable | Meaning | What to do |
|---|---|---|---|
operation_in_progress |
yes | Another lifecycle operation runs on the workspace (active_operation_id, active_operation_kind). |
Wait for it, then retry. |
email_registered |
no | POST /v1/auth/signup with an email that already has an account. |
Sign in (shard auth login). |
workspace_not_running |
yes | A tool token was requested while the workspace is suspending, or while a resume that was asked for has not started yet; or a browser stream while it is not running (observed_state, desired_state). A suspended workspace does get a tool token: the cell answers it only the reads of the workspace's disk. |
Open or resume the workspace, then request the token. |
workspace_deleted |
no | The workspace is deleted; the key can be reused after deletion finishes. | Use a new key. |
resize_not_available |
no | PATCH /v1/workspaces/{id}/caps is not available to this organization. Nothing changed. |
Set the caps when you open the workspace. |
not_running |
no | Suspend, or suspend when idle, of a workspace that is not running. | Refresh the workspace. |
already_running, not_suspended |
no | Resume of a workspace that is not suspended. | Nothing to do. |
not_snapshottable, not_forkable, not_resettable |
no | The workspace is not running or suspended. | Refresh the workspace. |
key_in_use |
no | The fork's target key is taken. | Choose another key. |
lifetime_mismatch |
no | The key is open with another lifetime (lifetime, requested_lifetime). |
Omit lifetime, or use another key. |
session_lifetime |
no | A session workspace cannot be suspended, now or when idle, and has no idle policy. | Close it, or fork it to keep its state. |
not_session |
no | Close of a persistent workspace. | Suspend or delete it instead. |
legacy_disk_layout |
no | Reset, save as template and changes need a layered workspace. |
Use a layered workspace. |
layout_unsupported |
no | The template version needs a disk layout that is not available here, or it cannot run a file-first workspace (disk_layouts). |
Use another template version. |
computer_use_disabled |
no | A tool token asked for the computer tool while the workspace's computer use is off. |
Switch computer use on for the workspace or its template, or leave computer out. |
computer_use_unavailable |
no | Switching computer use on for a workspace whose template version has no desktop. | Use python-node-browser 15 or later, or a template built on it. |
not_supported_for_mode, mode_mismatch |
no | A call that the workspace's mode does not have, or a reopen with the other mode (see file-first workspaces). | Use what the mode offers, or omit mode. |
template_not_layered |
no | A draft's base is not layered-capable. | Choose another base. |
suspend_capture_started |
no | A suspend cancel after the suspend's capture has started (details.progress). The suspend completes. |
Wait for the operation, then resume the workspace. |
suspend_cancel_unavailable |
no | A suspend cancel while the suspend is not in a phase that can be canceled (details.progress). |
Cancel while progress.cancelable is true, or wait for the operation. |
suspend_not_active |
no | A suspend cancel after the suspend has ended (details.progress). |
Read the operation. |
not_suspend_operation |
no | Suspend progress or cancel of an operation that is not a suspend. | Use the suspend's operation id. |
draft_exists |
no | The template already has a draft (workspace_id). |
Use that draft. |
draft_stale |
no | The template got a newer version since the draft was opened (latest_version, draft_base_version). |
Start a new draft from the latest version. |
build_in_progress |
yes | Another build of the template is unfinished (build_id). |
Wait for it, then publish. |
build_finished |
no | Cancel of a build that already finished. | Nothing to do. |
file_list_unavailable |
no | The version has no file list (published before file lists existed). | Open a draft or test instance to see its files. |
file_list_indexing |
yes | The file list is still being indexed. | Retry shortly. |
guest_feature_unavailable |
no | The workspace's template cannot do this (for example list changes). | Use a newer template version. |
package_index_unavailable |
pip: yes; apt: no | The base has no apt index, or the pip name index is not built yet. | Type the package name; the build checks it. |
template_archived, version_archived |
no | The template or version is archived. | Use another template or version. |
name_taken |
no | A secret with that name exists in the scope. | Choose another name, or rotate the existing secret. |
secret_deleted |
no | The secret is deleted. | Create it again. |
concurrent_rotation |
yes | Another rotation of the secret ran at the same time. | Retry. |
version_mismatch |
no | Egress policy or spend policy update with a stale If-Match (current_version). |
Read the policy again and re-apply. |
name_in_use |
no | A live volume of the project has that name. | Choose another name. |
volume_attached |
no | Delete of a volume that is still attached (attachments). |
Detach it, or delete with force. |
volume_creating, volume_not_available |
yes while creating | The volume is not available yet (volume_state). |
Wait until it is available. |
volume_not_shared |
no | The volume belongs to another project. | Use a volume of this project. |
volume_name_ambiguous |
no | A volume name in an open's volumes matches more than one shared volume (name). |
Select an unambiguous project volume name. |
already_attached, detach_in_progress, attach_in_progress |
attach_in_progress: yes |
The volume is already attached with another path or mode, or an attach or detach is still running. | Detach first, or retry shortly. |
mount_path_conflict |
no | The mount path equals or nests with another mount of the workspace. | Choose another path. |
Two retryable 409s have no reason: a request whose Idempotency-Key is still in progress, and an open of a key that
another open is creating at the same moment. Retry the same request (with the same key).
422 validation_failed
Workspaces, inputs and secrets:
| Reason | Meaning |
|---|---|
reserved_key_prefix |
Workspace keys starting with sf: are reserved. |
like_outside_project |
like does not name a live workspace in the caller's project (field: like). |
like_not_standard |
like names a workspace that is not a standard workspace (field: like). |
mount_path_conflict |
Two of an open's volumes have equal or nested mount paths (field: volumes). Choose distinct, non-nested absolute mount paths. |
mode_not_available |
This deployment does not offer file-first workspaces (field: mode). |
not_supported_for_mode |
lifetime: "session" for a file-first workspace, which is always persistent (field: lifetime), or allocation_mode: "elastic" for one (field: caps.allocation_mode); file-first executions have a fixed size. |
requires_elastic |
memory_mib_held for a file-first workspace, whose executions have a fixed size (field: caps.memory_mib_held). |
exceeds_memory_mib |
memory_mib_held above memory_mib (field: caps.memory_mib_held, or memory_mib_held on a resize). |
shrink_not_supported |
A resize to a smaller disk_gib (field: disk_gib, details.current_disk_gib); disks grow only. Nothing changed. |
confirm_destructive_required |
Reset needs {"confirm_destructive": true}. |
secret_not_available |
A secret name to bind is unknown or not usable by this workspace (details.names); nothing changed. |
env_collision |
A bound secret has the name of a template environment variable or text input (details.name). |
input_required |
A required input is missing (details.names, details.kind). |
input_unknown |
The template declares no input with that name (details.names). |
input_invalid |
An input value is not a string, is a secret input, is over 4096 bytes, or contains CR, LF or NUL. |
egress_widening |
A workspace egress policy allows more than the template's egress ceiling (outside). |
reserved_prefix, nul_character, too_large |
Secret names starting with SHARDFLUX_, a value with a NUL byte, or a value over the size limit. |
On a resize with at_least: true, a smaller disk_gib leaves the disk unchanged and allows the other named
resources to grow. A resize without that flag keeps the shrink_not_supported response for a smaller disk.
Workspace tools (the cell gateway):
| Reason | Meaning |
|---|---|
invalid_cwd |
An exec, execution or terminal start named a cwd that is not an absolute path (or has a NUL byte, or is over 4096 bytes); details.field is cwd. Relative paths are not resolved: the message names the absolute path the cwd likely means, e.g. use "/home/user/app". Nothing ran. |
Templates and builds:
| Reason | Meaning |
|---|---|
invalid_recipe |
The recipe does not match the recipe v2 schema (details.field). |
invalid_path, invalid_package, invalid_host |
A path, package name or host in the recipe is invalid (details.field). |
invalid_path at recipe.immutable or recipe.immutable[<i>] |
An immutable path is not an absolute, clean path of at most 256 bytes; is /, under /proc, /sys, /dev, /run or /tmp, or a parent of a path Shardflux owns (such as /etc, /usr or /var/lib); repeats or contains another; or the list has more than 8 entries (limit). Use directories of your own, such as /opt/acme. |
immutable_path_removed |
The new version leaves out an immutable path of the template's current version (details.removed, details.open_version). Versions keep every immutable path: add the paths back. |
immutable_paths_unsupported_base |
The build cannot carry the template's immutable paths (details.base, details.paths). Build from a template.yaml on python-node-browser 11 or later or ubuntu-24.04 2 or later; to save a workspace as a version, use a workspace created on such a base. |
invalid_settings |
settings do not match the settings schema, or settings.defaults was given together with defaults. |
base_not_found, base_archived, base_not_published, architecture_not_supported |
The recipe's base cannot be used. |
base_not_layered |
A recipe v2 build needs a layered-capable base. |
base_required |
A draft of a template without a published version needs base. |
language_unavailable |
The base does not offer that language or version (available). |
language_conflict |
The base already has another version of the language. |
too_many_files, too_many_steps, too_many_hosts, recipe_too_large |
The recipe is over a size limit (limit). |
platform_owned_path |
A file destination is owned by the platform. |
upload_required |
A file entry still names a local from path: upload it and send upload: "sha256:<hex>". |
upload_missing |
The organization has no upload with that SHA-256. |
upload_digest_mismatch |
The same SHA-256 was announced with another size, or the bytes do not match. |
upload_too_large |
An upload is over 5 GiB. |
extra_hosts_without_auto, allow_hosts_without_allowlist, allowlist_empty |
The build network settings are inconsistent. |
services_unsupported |
The base's guest agent cannot run services; remove settings.services or use a newer base. |
invalid_defaults |
An idle timeout without lifetime: "session". |
too_many_acknowledged_findings |
More than 200 acknowledged scan findings. |
platform_template_slug, reserved_template_slug, slug_not_supported_by_builder |
The slug cannot be used for an organization template. |
Egress policy validation returns every problem in details.issues, each with path, reason and message.
Spend policy (overage and its spend cap, changed by an owner or billing member; details.field names the field):
| Reason | Meaning |
|---|---|
overage_unavailable |
The plan has no overage (a trial, or no active subscription). |
spend_cap_required |
Turning overage on needs a spend cap. |
spend_cap_below_minimum |
The cap is below $1 (min_minor). |
spend_cap_above_plan_price |
The cap is above the plan price (max_minor). |
spend_cap_below_charges |
Overage already charged more than that this period (charges_minor). Turning overage off is always allowed. |
507 workspace_disk_full
For a reachable workspace with an exhausted filesystem, the files API can read and remove files without starting an exec session. Cleanup exec sessions use bounded memory-backed control storage and output; their session labels include shardflux.session_storage: memory_until_cold_boot. They survive an agent restart or memory resume; a cold boot ends the processes and clears those emergency session records. Ordinary exec sessions keep their durable storage. Freeing filesystem space restores ordinary writes and exec storage. If the workspace cannot resume, contact support with the request id; credential delivery still requires writable space.
503 dependency_unavailable
details.reason: budget_backend_busy or budget_policy_unavailable means request-budget accounting is temporarily unavailable. The error is retryable, with a one-second Retry-After and details.retry_after_seconds. Back off and retry; this does not mean the plan allowance is exhausted.
| Reason | Meaning |
|---|---|
package_search_unavailable, package_index_loading |
Package search is temporarily unavailable. Retry. |
uploads_not_configured |
Template uploads are not available in this environment. |
build_log_bucket_not_configured, signing_failed |
The full build log cannot be served right now; use the log tail on the build. |
File tools, parking and hosts
Reasons of the cell gateway for search, patches and reads of sleeping workspaces, for parked workspaces and for writes under immutable paths:
| Code and reason | Retryable | Meaning | What to do |
|---|---|---|---|
409 conflict revision_mismatch |
no | A patch's expected_revision is not the file's current revision (details.current_revision, absent for a missing file). Nothing changed. |
Read the file again and redo the edit. |
422 validation_failed edit_not_found |
no | An edit's old text does not occur in the file (details.index, counting from 0). Nothing changed. |
Fix that edit. |
422 validation_failed edit_ambiguous |
no | An edit's old text occurs more than once (details.index). Nothing changed. |
Include more surrounding text, or set replace_all. |
422 validation_failed edit_not_text |
no | The file is not UTF-8 text, so edits cannot apply. | Replace it with content. |
422 validation_failed patch_invalid |
no | The patch does not have exactly one of edits and content. |
Fix the request. |
409 workspace_not_running offline_unavailable, offline_budget |
yes | A read of a suspended workspace could not be answered from its disk. | Resume the workspace and read again. The SDKs, the CLI and the MCP server do. |
503 dependency_unavailable offline_changed |
yes | The disk changed during a read of a sleeping workspace, because it started running. | Retry: the running workspace answers. |
503 service_unavailable host_capacity |
yes | The workspace's host is making room to wake it. The gateway held the call while it did, for up to 25 seconds for a command, terminal or git start, 30 for an output or terminal attach, and 50 for other calls; details.waited_seconds says how long, and details.queue_position, when present, is the call's place in line. The call was not executed. |
Retry after Retry-After; the SDKs do for any call (TypeScript 0.22.0+, Python 0.18.0+). |
503 service_unavailable wake_failed |
yes | Waking the parked workspace failed; its state is intact. The call was not executed. | Retry. |
409 workspace_busy workspace_fenced |
yes | A lifecycle operation is taking over the workspace. The call was not executed. | Wait for the operation, then retry. |
409 conflict read_only_path |
no | A write, patch, mkdir, move or delete under one of the template's immutable paths. Nothing changed. |
Write to the workspace's own files; to change a tool, copy it there first. |
409 conflict host_feature_unavailable |
no | This call is not available for this workspace (details.feature: file_search or file_patch); its reads and stats come without revisions. |
Use another way: grep in a command instead of a search, a read and a write instead of a patch. |
Inbound ports
Reasons of the ports routes of the API:
| Code and reason | Retryable | Meaning | What to do |
|---|---|---|---|
403 forbidden inbound_ports_not_available |
no | Inbound ports are not enabled for the organization. | Contact support to enable them. |
404 not_found port_not_exposed |
no | A token, link or callback URL of a port that is not exposed (details.port). |
Expose the port first. |
409 conflict port_limit |
no | The workspace already exposes the most ports (details.limit). |
Close a port first. |
422 validation_failed port_reserved |
no | Port 61001 is the workspace desktop's control port; it cannot be exposed. | Watch the desktop with computer.stream(), which exposes its viewer ports. |
409 conflict not_supported_for_mode |
no | A file-first workspace has no processes to serve a port. | Use a processful workspace. |
Answers of a port URL itself carry the header x-shardflux-reason; a response without it came from
your app. The body is the usual error envelope (an HTML page when a browser asks for one):
| Status and reason | Retryable | Meaning | What to do |
|---|---|---|---|
401 port_auth_required |
no | The request carries no port token, link cookie or callback path. | Send Authorization: Bearer sfp_... or X-Shardflux-Token, or open a signed link. |
401 port_token_invalid |
no | The token is malformed, expired, or of the wrong kind for where it was sent (a link token in a header). | Mint a new token or link. |
401 port_token_revoked |
no | The port was closed (and maybe exposed again) since the token was minted, or its key or user was revoked. | Mint a new token or link. |
401 callback_invalid |
no | The callback URL was replaced or revoked. | Register the current callback URL. |
403 port_token_scope |
no | The token is for another workspace or port. | Use the token of this port. |
403 inbound_ports_not_enabled |
no | Inbound ports are not enabled for the organization. | Contact support. |
404 port_not_exposed |
no | The port is not exposed. | Expose it. |
404 unknown_host, reserved_path |
no | No workspace is served at this address, or the path is under /__shardflux/. |
Check the URL. |
413 body_too_large |
no | The request body is larger than 1 GiB. | Send less. |
429 rate_limited, connection_limit |
yes | Too many requests or open connections (Retry-After). |
Back off and retry. |
502 port_not_listening |
yes | The workspace runs, but nothing accepted connections on the port in time. | Start the server on 0.0.0.0:<port>, then retry. |
502 upstream_error |
yes | The app closed the connection without a complete answer. | Check the app's logs; retry. |
503 wake_timeout |
yes | The workspace did not run within 30 seconds (Retry-After). |
Retry. |
409 wake_failed |
no | The workspace's latest start failed and needs recovery. No new start was requested. | Contact support to recover the workspace, then retry. |
503 wake_failed, host_capacity |
yes | A transient wake failed (wake_failed), or the workspace's host was still making room to wake it after 30 seconds (host_capacity). Retry-After says when to retry. |
Retry. |
402 entitlement_required, card_required, allowance_exhausted; 403 quota_exceeded |
no | Starting the workspace was refused as a resume would be. | See the reason above. |
File-first workspaces
Reasons of the API and the cell gateway for file-first workspaces:
| Code and reason | From | Retryable | Meaning | What to do |
|---|---|---|---|---|
409 conflict not_supported_for_mode |
API, cell | no | The call does not exist for the workspace's mode (details.mode, details.operation): suspend, resume, snapshot, fork, reset, save as template, volumes, idle policies, exec sessions, terminals, processes, git, browser, changes or keepalives of a file-first workspace, or reading an execution of a processful one. |
Use what the mode offers. |
422 validation_failed not_supported_for_mode |
API, cell | no | lifetime: "session" when opening a file-first workspace, or execution_id / output_limit_bytes in an exec of a processful workspace. |
Leave the field out. |
409 conflict mode_mismatch |
API | no | The key belongs to a workspace of the other mode (mode, requested_mode). A mode never changes. |
Omit mode, or use another key. |
422 validation_failed mode_not_available |
API | no | This deployment does not offer file-first workspaces. | Use a processful workspace. |
409 conflict layout_unsupported |
API | no | The template version has no layered disks, which executions need (disk_layouts). |
Use a layered template version. |
409 conflict tree_revision_mismatch |
cell | no | If-Match names another revision than the tree's (details.current_tree_revision). Nothing changed. |
Read what changed, then retry with the new revision. |
404 not_found, 422 validation_failed outside_tree_root |
cell | no | A read (404) or a change (422) of a path outside /home/user (details.tree_root). |
Use a path under /home/user. |
409 workspace_busy execution_in_progress |
cell | yes | An execution runs (details.execution_id); a second execution or a file change must wait. |
Retry when it has ended. The SDKs and the CLI wait. |
409 conflict execution_id_reused |
cell | no | The execution id was already used for a different request. | Use a new id for a new command. |
503 service_unavailable no_execution_host |
cell | yes | The execution cannot be placed right now (Retry-After). Nothing ran, and the id stays unused. |
Retry with the same id. The SDKs and the CLI do. |
An execution that ended failed or lost published nothing. Its result carries error with code, message,
retryable (whether a new execution may succeed) and details.reason, for example exec_failed_to_start or
layout_unsupported.
With a person's session
Account actions on /v1 need a person's CLI session (sfu_..., from /v1/auth or shard auth login); an API key
gets 403 forbidden for them. These are the reasons they, and signing up, add to the tables above:
| Code and reason | Meaning | What to do |
|---|---|---|
409 conflict subscription_exists |
Checkout for an organization that already has a subscription. | Change plans in the billing portal (POST .../billing/portal-sessions, shard billing portal). |
409 conflict not_creator, trial_active, trial_identity_required, trial_used, trial_unavailable |
A card check (POST .../billing/card-checks) by someone other than the organization's creator, or for an organization that already has a card on file and is on its trial, awaits GitHub or work-domain verification (trial_identity_required), has used its creator's trial (one signup trial per person, per card and per GitHub account across all accounts, ever), or has no trial to start (details.trial). |
The creator verifies GitHub or a work domain, or keeps going on the trial, or chooses a paid plan (GET /v1/billing/catalog). |
409 conflict checkout_superseded |
A concurrent checkout request for another plan replaced this one. | Retry. |
409 conflict subscription_active, checkout_pending |
Organization deletion while a subscription is active (billing_state, current_period_end) or a Checkout session is open (checkout_id, expires_at). |
Cancel the subscription in the billing portal, or let the checkout finish or expire. |
409 conflict organization_ownership |
Account deletion while you are the only owner of an organization with other members, or its only member (details.organizations). |
Transfer ownership of those organizations, or delete them. |
409 conflict last_owner |
A role change or removal would leave the organization without an owner. | Make someone else an owner first. |
409 conflict concurrent_invitation |
An invitation to the same address is being created at the same moment. | Retry. |
409 conflict export_expired |
The data export's download is past its expiry (expires_at). |
Create a new export. |
409 conflict export_too_large |
An audit export would exceed its row limit (max_rows, matched_rows_at_least). |
Narrow the time window or the filters. |
422 validation_failed email_disposable |
Signing up (shard auth register), or changing the sign-in email, with an address at a disposable-email domain or a subdomain of one (field: email). |
Use a permanent email address. |
422 validation_failed plan_not_purchasable |
Checkout for a plan that cannot be bought, such as the signup trial (field: plan_key). |
Choose a paid plan from GET /v1/billing/catalog. |
429 rate_limited mfa_locked |
Too many wrong two-factor codes (retry_after_seconds). |
Wait, or reset the password by email. |
Operation errors
A failed operation (state: failed or canceled) carries error: {code, message, retryable, details}. The SDKs raise
it as OperationFailedError (errorCode, retryable).
error.code |
Retryable | Meaning | What to do |
|---|---|---|---|
capacity_unavailable |
yes | The start passed its deadline: 15 minutes after its creation, later while new capacity is starting for it or its files are being moved to new storage (at most an hour; details.reason: preserving_files for the second, with the files kept), or after 2 minutes when no more capacity can be added right now (details.retry_after_seconds). A start that would join a full start queue gets it at once, with retry_after_seconds, limit (queued_starts, or organization_queued_starts for the organization's share) and limit_value. Nothing was started; a suspended workspace stays suspended. details: reason, waited_seconds, required, pool, and deadline_seconds, or retry_after_seconds when it fails early. |
Send the same request again, after retry_after_seconds when it is set. |
invalid_resources |
no | The start asks for more CPU, memory or disk than the largest workspace size (details.reason: exceeds_host_class, required, host_class, exceeded). |
Open with smaller caps. |
startup_failed |
yes | A start command or service of the template failed (details.reason: startup_failed, service_not_ready, secrets_unavailable, secret_not_available, guest_feature_unavailable). The workspace keeps running for inspection; startup names the step, exit code and output tail. |
Fix the cause; the next open runs the failed step again. |
workspace_storage_unavailable |
retryable says |
The workspace's storage cannot be reached. With details.reason: host_lost: its host failed (details.workspace_state: suspended; the workspace stays suspended). Retryable, with details.retry_after_seconds, while its disk is being moved to a new host; not retryable when neither its disk nor a checkpoint could be restored. See If a workspace's host fails. Otherwise the storage is out of reach for now. |
host_lost, retryable: resume again after retry_after_seconds. Not retryable: contact support with the workspace ID; a later resume restores the workspace once its disk is available again. Otherwise send the request again later. |
restore_stalled |
yes | The resume did not finish within 10 minutes. The workspace stays suspended with its files and its latest checkpoint (details.workspace_state: suspended, checkpoint_id, retry_after_seconds). |
Resume the workspace again after retry_after_seconds. |
checkpoint_capture_failed |
yes | The checkpoint could not be captured. With details.reason: snapshot_prepare_timeout, preparation exceeded its time budget; with checkpoint_space_unavailable, the suspend did not start because its checkpoint could not be written right now (details.retry_after_seconds). Either way the workspace keeps running with its memory and processes, and its previous checkpoint is kept. |
Continue working; retry the suspend later with backoff (after retry_after_seconds when it is set). |
suspend_failed |
no | The suspend could not complete and the workspace could not be put back to running (details.workspace_state: failed). Its disk is kept. |
Open its key or resume it: the start boots its disk again, with every process restarted. |
resume_required |
no | A fork or snapshot of a workspace that was suspended when its host failed and has not run since. Its newest files are on its disk, which the next resume boots. | Resume the workspace (or open its key), then fork or snapshot it. |
template_unavailable |
no | The workspace's template version could not be installed for the start, or a layer of it did not verify against the version's manifest (details.reason: template_layer_unverified). Nothing was started; a suspended workspace stays suspended with its files. |
Open new workspaces on another template version; contact support with the workspace ID to start this one. |
layout_unsupported |
no | The template version's disk layout is not supported. | Use another template version. |
dependency_failed |
no | The operation depended on one that failed (for example a draft capture behind a test instance). | Check the other operation. |
layer_capture_failed |
no | Capturing a draft state or a template source failed. | Retry the capture. |
workspace_active |
no | A suspend when idle found the workspace in use when it was due (a tool call after the request, or an attached stream; details.reason: tool_call_after, stream_attached, tool_in_flight, active_within). The operation is canceled; nothing changed and the workspace keeps running. OperationFailedError.workspaceActive (TypeScript 0.12.0+), .workspace_active (Python 0.8.0+). |
Nothing: it is not a failure. Ask again at the end of the next turn. |
suspend_canceled |
no | The suspend was canceled before its capture (shard operations cancel, cancelSuspend(), cancel_suspend(), workspace_suspend_cancel). The operation is canceled; the workspace keeps running as it was, with its memory and processes. |
Nothing: carry on in the workspace. |
Durable copy of a suspend
A suspend succeeds once the workspace is sealed on its host; its checkpoint then reaches durable storage
(Durable storage). The suspend operation stays succeeded in every case; its
result.durability.state tells the rest.
durability.state |
Meaning | What to do |
|---|---|---|
pending (with overdue_at) |
The copy is taking longer than durable_by; it keeps going. |
Nothing, or keep waiting with durable: true. |
lost |
The host was lost before the copy committed (reason, for example host_gone). The previous durable checkpoint stays the recovery point, and the next resume restores it and reports lost_suspend. With no earlier checkpoint, that resume fails with checkpoint_not_found. |
Redo the work done since lost_suspend.state_as_of. |
Waiting for the copy (suspend({ durable: true }), waitForDurable(), suspend(durable=True), wait_for_durable()):
DurabilityLostError(TypeScript 0.12.0+, Python 0.8.0+):durability.statebecamelost. It carries the operation anddurability(withreason).OperationTimeoutErrorwithdurable: true: the wait'stimeoutMs/timeoutran out while the copy was pending. The suspend has succeeded and the copy continues; wait again withwaitForDurable(err.operationId)/wait_for_durable(err.operation_id).- The CLI (
--durable, 0.7.0+) and the MCP server (durable: true, 0.6.0+) report both as errors naming the operation anddurability.state: codedurability_lost(details.statelost,details.reason; CLI exit 6) and codetimeoutwithdetails{durable: false, state: "pending"}(CLI exit 5; keep waiting withshard operations wait <id> --durableoroperation_waitwithdurable: true).
While an operation is capacity_pending, error.details shows where it stands: queue_position (its place in line),
deadline_at (it moves later while new capacity starts) and pool.scale_out (requested while new capacity is
starting, with pool.expected_ready_by).
Operation state_reason values are progress, not errors: no_ready_host while capacity_pending (waiting for a
host), storage_host_unavailable while capacity_pending (the workspace's disk is being reattached; the start
continues by itself), preserving_files while capacity_pending (the workspace's files are being moved to new
storage; the start continues by itself once they are, and its deadline_at is an hour after its creation),
lease_renewing while capacity_pending (the host is picking up the organization's current
usage allowance; the start continues by itself), template_downloading while running (the host is fetching the
template).
Template builds from a workspace or a draft report failure.code on the build: source_unavailable (the capture
failed or the checkpoint is gone), resource_limit (details.limit: template_org_bytes_max or
template_file_entries_max), scan_failed (the credential scan found secrets: the paths are listed; build again with
acknowledged_scan_findings), compatibility_failed and publish_failed.
Builds of a template with immutable paths, from any source, also report:
failure.code |
Meaning | What to do |
|---|---|---|
immutable_path_missing |
An immutable path is not a directory in the built filesystem (details.path): it is missing, a file or a symbolic link. |
Create the directory in the build, with a files entry or a step. |
immutable_image_too_large |
The immutable paths hold more than 16 GiB (details.value, details.actual). |
Keep large data in the template's other directories. |
Client errors
The clients add their own errors for failures that never reached the API:
- TypeScript SDK:
ExecStartError(0.10.0+: a command that could not start; aShardfluxApiErrorwithexec_failed_to_start),OperationFailedError,OperationTimeoutError,DurabilityLostError(0.12.0+),ShardfluxProtocolError,ToolArgumentError,CheckoutTimeoutError(0.9.0+) and the template errors (reference).NotSupportedForModeErrorandTreeRevisionMismatchError(0.9.0+) areShardfluxApiErrors fornot_supported_for_modeandtree_revision_mismatch; the SDK raises the first before sending a call the mode does not have (local: true). - Python SDK: the same classes under
ShardfluxError, withCheckoutTimeoutError,NotSupportedForModeErrorandTreeRevisionMismatchErrorfrom 0.5.0,ExecStartErrorfrom 0.6.0 andDurabilityLostErrorfrom 0.8.0 (reference). - CLI: exit codes and local codes such as
usage,interruptedand (0.5.0+)checkout_endedandexecution_failed(an execution that endedfailedorlost, exit 6) (reference). - MCP server:
invalid_arguments,workspace_pinned,timeout,operation_failedand others. An error result carries the refusal'sreason, and from 0.4.0 ahintfor the file-first and host refusals (reference).
Do not infer workspace deletion from 404
A bodiless/proxy 404 is a protocol failure. Even a structured not_found can mean a stale route or inaccessible scope. TypeScript 0.12.0 and Python 0.8.0 protocol errors include the responding source. isWorkspaceGone(err) / is_workspace_gone(err) is true only for an explicit API conflict with reason workspace_deleted. It is never true for a protocol error or cell not_found. Do not delete user data based on HTTP status alone.
failed is recoverable through resume or auto-wake with the same workspace ID and disk. It is not a tombstone. The workspace's failure carries the error of the operation that failed it (for example vm_start_failed with details.host_reason) until a start succeeds. Conditional idle suspension that loses the race to work is canceled before capture (workspace_active), leaving the workspace running.
Retention validation
422 validation_failed, details.reason: retention_requires_persistent means retention was requested for a
session workspace. Use a persistent workspace to keep its state and have it deleted after 1 through 3650 idle
days. Day values outside that integer range are 422 validation_failed. A retention setter on a deleted
workspace returns 409 conflict, details.reason: workspace_deleted; open its key to start a fresh workspace.
Missing files
TypeScript SDK 0.16.0+ raises FileNotFoundError, a ShardfluxApiError, when a requested file path is missing.
Python 0.12.0+ exposes shardflux.FileNotFoundError, inheriting both builtin FileNotFoundError and
ShardfluxApiError. Catch it to create a file or ask for another path. Workspace deletion and protocol errors
remain separate errors.
Tunnel replay
A cell conflict with details.reason=tunnel_replay_unavailable means the tunnel's retained output is unavailable. Open a fresh forward connection. Reverse tunnel handles in TypeScript SDK 0.27.3+, Python 0.24.0+ and CLI 0.24.1+ replace the listener automatically; their existing TCP peers reset.