Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Errors and status codes

This page maps every error across the Rust API, the Python API and the server's gRPC and HTTP APIs, and says what a caller should do about it.

Submission errors

SubmitErrorPython SubmitError.codegRPCHTTPCauseWhat to do
UnknownPool(name)unknown_poolNOT_FOUND404No pool with that name.Fix the pool name.
UnknownResource(name)unknown_resourceNOT_FOUND404No resource with that name.Fix the resource name.
ResourceExceedsCapacity(name)resource_exceeds_capacityINVALID_ARGUMENT400Requested units exceed the resource's permits or burst, so it could never run.Request fewer units.
ReservationConflictreservation_conflictFAILED_PRECONDITION409Fixed-start reservations would exceed the pool's reservable capacity at that time.Pick another start time, raise reservable_fraction or max.
CyclecycleINVALID_ARGUMENT400The dependencies form a cycle.Fix the graph.
UnknownDependency(ref)unknown_dependencyNOT_FOUND404Dependency on an unknown id, or an unknown key when the pool rejects unknown dependencies.Submit the parent first, or set await_unknown_dependencies_s.
FullfullRESOURCE_EXHAUSTED429Queue bounds reached with the wait overflow policy. In-process callers wait for space instead of seeing this.Retry with backoff.
OverloadedoverloadedRESOURCE_EXHAUSTED429Bounds reached with reject, or shedding found nothing lower to evict.Retry with backoff, or shed load upstream.
DuplicateKeyInBatch(key)duplicate_key_in_batchINVALID_ARGUMENT400A batch defines the same new key twice.Deduplicate the batch.
Invalid(reason)invalidINVALID_ARGUMENT400Malformed spec: zero resource units, max_attempts = 0, NUL in a string, ...Fix the request.

The server also returns INVALID_ARGUMENT / 400 for requests that fail its own checks ([limits], unknown JSON fields, both relative and absolute times) and PERMISSION_DENIED / 403 for pools or tenants outside the credentials' scope.

Task outcomes

Awaiting a task (TaskHandle in Rust, await handle in Python, Wait or GET .../result on the server) ends with one of:

StatusRustPythonMeaning
succeededOk(Arc<O>)the return valueThe executor returned an output.
succeeded (output gone)Err(TaskFailure::OutputUnavailable { id })TaskFailed(status="succeeded")Succeeded, but the output is no longer cached or stored (for example a duplicate submission long after the original finished). Server: output_available = false.
failedErr(Ended { status: Failed, .. })TaskFailed(status="failed")Permanent error, or a fixed start missed with on_miss = fail.
dead_letteredEnded { status: DeadLettered, .. }"dead_lettered"Retryable failures used up every attempt.
cancelledEnded { status: Cancelled, .. }"cancelled"Cancelled by a caller.
skippedEnded { status: Skipped, .. }"skipped"A dependency edge can no longer be met, an unknown dependency was awaited too long, or a recurring occurrence overlapped the previous one.
missedEnded { status: Missed, .. }"missed"Fixed start missed with on_miss = skip.
shedEnded { status: Shed, .. }"shed"Evicted by the shed_lowest / shed_oldest overflow policy.
–Err(TaskFailure::Shutdown)"shutdown"The scheduler stopped before the task finished. Server: UNAVAILABLE / 503, wait again on the new leader.

message carries the last error message (or the status name).

Executor errors

What an executor returns decides whether the attempt is retried:

Rust TaskErrorPythonRemote worker (Fail)Effect
Retryable { message, retry_after }raise Retryable(msg, retry_after=s), or any other exceptionpermanent = false, retry_after_sRetried per the retry policy, not before retry_after.
Permanent { message }raise Permanent(msg)permanent = trueFails the task now (failed).
CancelledCancelledError caused by cronoqCancelledThe attempt was aborted; the engine already decided what happens next.
panic in a Rust executor–panic in a run_worker handler: retryableIn-process: permanent failure (Failed). The scheduler keeps running.

Timeouts (timeout) and lease expiries (no heartbeat within lease_ttl) are retryable failures too.

Scheduler errors

Rust ErrorPythonMeaning
Submit(SubmitError)SubmitErrorSee above.
ShutdownSchedulerErrorThe scheduler has stopped or is stopping.
Config(String)ValueError (at construction) or SchedulerErrorInvalid configuration: no pools, missing executor, duplicate pool, min > max, bad cron or time zone, recurring template with one-shot fields.
Store(StoreError)SchedulerErrorPersistence failed fatally (see below).
Internal(String)SchedulerErrorInternal failure, for example the scheduler task panicked.

Python raises RuntimeError when a scheduler is used before start(), and SchedulerError when its event loop was closed without a shutdown.

Store errors

StoreErrorDriver behaviour
Unavailable(String)Transient (connection lost, timeout, database restarting). Retried with capped backoff for up to max_store_outage (default 5 min) while the engine keeps running; replies and dispatches that need durability wait. After that the scheduler stops with Error::Store.
Other(String)Retried a few times (5), then the scheduler stops.
FencedAnother instance took leadership. The scheduler stops immediately and writes nothing more.

The server reports store failures as UNAVAILABLE / 503 and logs the details; it does not return database messages to clients.

Client errors (Python cronoq.client)

ExceptionWhen
ClientErrorA gRPC call failed. .code is the lower-case status name (unauthenticated, permission_denied, not_found, resource_exhausted, failed_precondition, invalid_argument, unavailable, data_loss, ...).
TimeoutErrorClient.wait(timeout=...) or submit(..., call_timeout=...) ran out of time.

Server status codes

gRPCHTTPRetry?Cause
INVALID_ARGUMENT400noMalformed request or spec.
UNAUTHENTICATED401noMissing or unknown credentials.
PERMISSION_DENIED403noRole, pool scope, tenant or worker labels do not allow it, or authentication is off (without --insecure-no-auth) and the request does not name a loopback host.
NOT_FOUND404noUnknown task, pool, resource or dependency.
ALREADY_EXISTS409noThe key is already used by a task in a pool outside the credentials' scope.
FAILED_PRECONDITION409after changing the requestFixed-start reservation conflict; a task or pool served by another shard that this server cannot forward to (the response names the shard); a key that names tasks in several shards (use the id); a worker with an older protocol version.
–413, 415noBody too large, wrong content type.
RESOURCE_EXHAUSTED429yes, with backoffQueue bounds reached.
INTERNAL500maybeUnexpected server error.
UNAVAILABLE503yes, with backoffNot the leader, shutting down, store unavailable.
DEADLINE_EXCEEDED504wait againA wait timed out while the task is still running; over HTTP, also a request that exceeded its time limit.
CANCELLED–maybeA gRPC call ran longer than limits.max_wait_s + limits.request_timeout_s.
DATA_LOSS–reconnectA Watch stream fell behind.

Submissions with a key are idempotent while the original task is live, and after it finished for as long as the server keeps its key (limits.key_retention_s, default 3600 s, and at most limits.key_retention_max finished tasks). Within that window, retrying a submit after UNAVAILABLE or a lost connection does not create a second task: the retry returns the original id with duplicate = true.