Skip to content

Error Catalog

Actions 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 connector.

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.

A representative slice of the error codes and how they surface:

Code HTTP GraphQL
timeout 504 TIMEOUT
authentication-error 401 UNAUTHORIZED
permission-denied 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
ambiguous-binding 400 (no GraphQL code — see below)
binding-missing 400 (no GraphQL code — see below)

compensation-error is the rollback-failed code — it surfaces when a saga compensation itself fails, which is the situation you most want surfaced loudly.

ambiguous-binding and binding-missing fire only on a direct connector/entity invoke that binds via on= — never at flow-YAML step or run level. ambiguous-binding is raised when a bare on=<env> resolves more than one connection of the connector’s type; binding-missing is its sibling for when on= resolves none. Both carry category permanent, so neither is retried.

These two are the only rows in the table above that are not platform AppError codes — they are structured per-result ErrorEntry codes attached to a failed connector result, so they never travel through the AppError→GraphQL-code mapping and no code path emits BAD_USER_INPUT for them. What each surface actually returns:

  • REST and MCP — the kebab code above, plus a machine-readable hint.candidates block: aliases for ambiguous-binding (the valid on=<env>/<alias> values), envs and connections for binding-missing. The HTTP status is 400 because a code with no specific status folds to max 400.
  • GraphQL connector invoke — no GraphQL error at all. The mutation returns a successful InvokeConnectorPayload with success: false and errors as bare message strings; code, category and hint are dropped. Check success, don’t look for an error extension.
  • GraphQL entity operations — an EntityError in the payload carrying code: "ambiguous-binding" (the kebab wire code, not a SCREAMING_CASE GraphQL code) and category: "permanent".

A step naming an unbound alias is a compile-time error instead (V1315); a run passing an undeclared environment is a separate, declared-set membership error.

In practice ~95% of connector failures are a single ConnectorError whose message determines its class. These classes are what a connector declares in its <name>.connector.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}"
locked 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

A failed run 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.