Errors and retries
Error classes in @cubicle/sdk (CubicleError, CubicleApiError, CubicleConnectionError, CubicleTimeoutError, CubicleBoxError, CubicleOperationError) with their codes and fields, branching with instanceof, automatic retries with Retry-After and backoff, what is safe to repeat, and idempotency keys.
Error classes
Every error the SDK raises is a CubicleError with a
stable, machine-readable code and a readable message. Branch on the class and the code, not
on the message.
| Class | code values | When | Useful fields |
|---|---|---|---|
CubicleApiError | The API's code, such as not_found, conflict or rate_limited; http_<status> when the response had none | The API answered with an error status, after any retries. | status, detail, retryAfterSeconds |
CubicleConnectionError | connection_failed, request_timeout, invalid_response | The API could not be reached, did not answer within requestTimeoutMs, or sent a success response that is not JSON. | cause |
CubicleTimeoutError | wait_timeout | A wait ran out of time. | last |
CubicleBoxError | box_failed, restart_required, hibernation_failed, box_superseded | A Box cannot reach the state being waited for. | box |
CubicleOperationError | operation_failed, operation_unknown | An exec, snapshot or restore operation ended failed or unknown. | operation |
CubicleError | missing_api_url, missing_api_key, invalid_option, org_required, missing_result | A configuration problem, or a finished operation whose result the SDK could not find. | code |
- An aborted
signalis not aCubicleError. The call rejects with the signal's reason; see Stop waiting with a signal. lastis the most recentBoxInfoorOperationa timed-out wait read.boxis the Box's latest record, andbox.observed?.errorsays why a Box failed.operation.errorsays why an operation failed.operation_unknownmeans an exec was interrupted: its Node's service restarted, or the Box was stopped, hibernated or deleted, or its host restarted, while the command ran. An exec is never replayed, so the command may or may not have run.- The
CubicleBoxErrorcodes are explained in Wait for a state. org_requiredis explained in The default organization, andinvalid_optionin Client options.- A
CubicleApiErrorhas the API'sstatus,codeandmessage. Every code, with its cause and what to do, is in Errors, and each method's reference, such asBoxes, lists the codes it rejects with. The SDK may retryrate_limitedandcapacityfirst; see Retries.
Branch on errors
import {
Cubicle,
CubicleApiError,
CubicleBoxError,
CubicleConnectionError,
CubicleOperationError,
CubicleTimeoutError,
} from "@cubicle/sdk"
const cubicle = new Cubicle()
try {
const result = await cubicle.boxes.exec("acme/job-1", ["make", "test"], { wake: true })
console.log(result.exitCode) // a non-zero exit code is a result, not an error
} catch (error) {
if (error instanceof CubicleOperationError) {
// operation_failed: operation.error says why. operation_unknown: it may or may not have run.
console.error(error.code, error.operation.error)
} else if (error instanceof CubicleTimeoutError) {
console.error("timed out; last record read:", error.last)
} else if (error instanceof CubicleBoxError) {
console.error("the Box did not become ready:", error.code, error.box.observed?.error)
} else if (error instanceof CubicleApiError && error.code === "not_found") {
console.error("no Box acme/job-1")
} else if (error instanceof CubicleApiError) {
console.error(error.status, error.code, error.message)
} else if (error instanceof CubicleConnectionError) {
console.error("cannot reach the Cubicle API:", error.code)
} else {
throw error // for example, an aborted signal's reason
}
}Retries
The SDK retries each request up to maxRetries times, 3 by default, after these failures:
| Failure | Delay before the retry |
|---|---|
429 rate_limited | The Retry-After value |
502, 503 or 504 | Retry-After if sent, otherwise a backoff |
A connection failure, or no answer within requestTimeoutMs | A backoff |
- Any other status, including
403 blocked, reaches you at once. - A
Retry-Afterlonger than 60 s is not slept. The error reaches you at once, withretryAfterSecondsset. - The backoff is exponential with jitter: 0.25 to 0.5 s before the first retry, doubling each time
up to 4 to 8 s. With the default 3 retries and no
Retry-After, a failing request spends 1.75 to 3.5 s in backoff before its error reaches you. 503 capacityis retried like any503, so it reaches you after those retries.maxRetries: 0turns retries off. Aborting thesignalalso ends a retry delay.- A wait's polls do not use
maxRetries: they keep polling until the wait's timeout. See Waiting.
A request that failed without an answer may still have reached Cubicle. The SDK repeats a request only when that is safe, and it makes every request it sends safe to repeat:
- Reads and
deleteare safe to repeat.deletelooks the Box up first and deletes it by ID, so a retry cannot reach a newer Box that took the name, and a retry that finds the Box already deleted counts as success. create,exec,snapshotandrestorecarry an idempotency key.start,stopandhibernatetake no key, but they set a desired state, so repeating one leaves the same state. A repeatedhibernatehas no further effect. Astartorstoprepeated after a lost response advances the Box's generation once more, which pauses traffic to a running Box without restarting it.
Repeated rate-limit refusals block your IP address; see Rate limits.
Idempotency keys
create, exec, snapshot and restore send an Idempotency-Key header. The SDK generates a
key for each call and sends the same key on every retry of that call. A retry after a lost
response therefore returns the original Box or operation: it never creates a second Box or runs
a command twice.
Pass your own idempotencyKey to make a whole call repeatable, for example when your job restarts
and runs the same step again. A repeated call with the same key and parameters returns the
original Box or operation, and then waits for it as usual:
// Safe to run again after a crash: a second run returns the first run's result.
const result = await box.exec(["sh", "-c", "make migrate"], {
idempotencyKey: "migrate-2026-11-05",
})The rules for keys, such as their length, how long they are kept and what a repeat returns, are in Idempotency keys. Recover a failed create shows a complete example.
Waiting
How @cubicle/sdk waits for Boxes and operations: what each call waits for and its default timeout, wait: false, timeoutMs, AbortSignal, waitUntil and waitUntilReady, polling through outages and rate limits with maxPollsPerSecond, and recovering a failed create with idempotencyKey.
SDK reference
The Cubicle and error classes, and every type the @cubicle/sdk package exports.

