Error Catalog
Operations never throw — each returns a typed result, either success with a payload or a typed error. That uniform contract is what lets the engine apply consistent retry, rollback, and HTTP/GraphQL mapping across every operator.
Error categories
Section titled “Error categories”Each error falls into a category that drives engine behaviour:
| Category | Meaning | Retried? |
|---|---|---|
| Transient | Temporary — timeout, rate-limit, connection, upstream-unavailable, lock conflict. | Yes |
| Permanent | Won’t succeed on retry — bad input, auth failure, not-found, parse error, bad config. | No |
| Infrastructure | Platform-level — file I/O, storage, backup failure. | No |
| Business | Flow-level — step failure, flow error, human-task, staging, share. | No |
The engine retries Transient errors per the step’s retry: policy; once attempts run out, a retry-exhausted error wraps the last failure.
HTTP and GraphQL mapping
Section titled “HTTP and GraphQL mapping”A representative slice of the error codes and how they surface:
| Code | HTTP | GraphQL |
|---|---|---|
timeout | 504 | TIMEOUT |
authentication-error | 401 | UNAUTHORIZED |
authorization-error | 403 | FORBIDDEN |
invalid-parameters | 400 | BAD_USER_INPUT |
invalid-configuration | 400 | BAD_USER_INPUT |
not-found | 404 | NOT_FOUND |
parse-error | 400 | BAD_USER_INPUT |
storage-error | 500 | INTERNAL_ERROR |
step-invocation-error | 500 | INTERNAL_ERROR |
staging-error | 500 | STAGING_ERROR |
compensation-error | 500 | INTERNAL_ERROR |
retry-exhausted | 500 | INTERNAL_ERROR |
compensation-error is the rollback-failed code — it surfaces when a saga compensation itself fails, which is the situation you most want surfaced loudly.
Error classes (for operators)
Section titled “Error classes (for operators)”In practice ~95% of operator failures are a single OperatorError whose message determines its class. These classes are what an operator declares in its definition.yaml:
| Class | Category | Retryable | Typical message |
|---|---|---|---|
validation | Permanent | No | "{Field} is required" |
not-found | Permanent | No | "{Entity} '{id}' not found" |
auth | Permanent | No | "Authentication failed", "Invalid API key" |
timeout | Transient | Yes | operation exceeded its time limit |
rate-limit | Transient | Yes | HTTP 429, "Rate limit exceeded" |
connection | Transient | Yes | "Connection refused", "Connection reset" |
unavailable | Transient | Yes | HTTP 502/503/504, "overloaded" |
api-error | Permanent | No | "{API} error ({code}): {message}" |
parse | Permanent | No | "Invalid JSON", "Failed to parse" |
config | Permanent | No | "{Field} is required when {Condition}" |
conflict | Transient | Yes | "Database locked", "File is locked" |
quota | Permanent | No | "File too large", "Max results exceeded" |
storage | Infrastructure | No | "Path does not exist", S3/disk errors |
How this shows up while authoring
Section titled “How this shows up while authoring”A failed invocation reports the error code and category in its log and in the REST/GraphQL response body. Transient failures retry automatically; permanent ones fail fast so you fix the flow rather than waiting through retries. See Common Pitfalls for the most frequent permanent (validation / invalid-parameters) cases and their fixes.