Skip to main content

Error handling

What fails a workflow​

An execution ends as Failed when:

  • A node returns a failure result (NodeResult::fail(...)) — for example a required field missing, an Eloquent record not found, fail_on_error on an HTTP node hitting a 4xx/5xx, division by zero.
  • A node's handle() throws and its retry budget is exhausted (or it has none).
  • A Stop node runs with outcome: failure.

$execution->error_message carries the reason. Node-level detail is in the execution log — the failing node's row has status: error (or failed) and its error field set.

An execution ends as Cancelled (not an error) when a Stop node runs with outcome: success.

Failures inside a queued run or resume are captured as a Failed execution record — the job itself doesn't rethrow.

Retries​

Any node can be given retry behaviour through its advanced settings (max_retries, retry_delay, retry_backoff). Retries only apply when handle() throws — a returned NodeResult::fail(...) is a deliberate failure and is not retried.

Observing failures​

$execution = Workflow::run('flag-large-orders', $payload);

if ($execution?->status === ExecutionStatus::Failed) {
Log::error("Workflow {$execution->workflow_id} failed", [
'execution_id' => $execution->id,
'reason' => $execution->error_message,
]);
}

Or query after the fact:

use Qanna\WorkflowEngine\Models\WorkflowExecution;

WorkflowExecution::where('status', ExecutionStatus::Failed)
->latest('started_at')
->limit(100)
->get();

Hook workflow.finished to react to every terminal outcome centrally — see Hooks.

Exceptions you might catch​

ExceptionThrown byWhen
RuntimeExceptionWorkflow::run() / runDraft() / resume()Running an unknown workflow id; running a workflow that has never been published; resuming an unknown execution id; resuming a non-suspended execution.
LogicException$workflow->publish()The model has unsaved changes to its content. Save it first.
Illuminate\Database\Eloquent\ModelNotFoundException$workflow->rollbackTo()The version number doesn't exist.

Workflow::dispatch() doesn't check the workflow up front. If the id is unknown or the workflow has never been published, the queued job throws in the worker and fails like any other queued job — no execution record is created.