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_erroron 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->errorMessage 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 job (ExecuteWorkflowJob, ResumeExecutionJob) 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->workflowId} failed", [
'execution_id' => $execution->id,
'reason' => $execution->errorMessage,
]);
}
Or query after the fact:
app(ExecutionRepositoryContract::class)
->listByStatus(ExecutionStatus::Failed, limit: 100);
Hook workflow.finished to react to every terminal outcome centrally — see
Hooks.
Exceptions you might catch
| Exception | Thrown by | When |
|---|---|---|
RuntimeException | ExecutionManager | Running/resuming an unknown workflow or execution id; resuming a non-suspended execution. |
WorkflowAlreadyExistsException | Workflow repo | create() with a duplicate id. |
WorkflowNotFoundException | Workflow repo | update() on a missing workflow. |
ExecutionNotFoundException | Execution repo | update() on a missing execution. |
BranchAlreadyOccupiedException | WorkflowDefinitionBuilder | Wiring a second edge onto a branch that's already taken. |
Storage exceptions live under Qanna\WorkflowEngine\Storage\Exceptions.