NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages
Workflows for Laravel

Troubleshooting

Start with php artisan workflows:doctor (add --json in CI, --strict to fail on warnings), then workflows:list --status=failed and workflows:show <ulid> for one instance’s path, step log, waits and next escalation.

Doctor findings

Levels are the DoctorLevel enum (ok, warn, fail):

CheckLevelFindingFix
key typesok / failA registered subject’s key doesn’t fit subject_key_type.Set workflows.subject_key_type (or key_type) to the model’s key type before migrating.
owner key typefailThe teams model’s key doesn’t fit owner_key_type.Set workflows.owner_key_type to uuid / ulid.
queuewarnQueue off, or the connection is sync.Fine for local work; in production turn the queue on with a real connection.
queue timeoutfailretry_after ≤ the step job timeout.Raise the connection’s retry_after above engine.lease − 30 s, or lower engine.lease.
pcntlwarnThe worker can’t enforce the job timeout.Install the pcntl extension on workers.
cachewarnThe default cache store is array or file.Use a store every server shares (redis, database, memcached, dynamodb) for onOneServer().
wakewarnworkflows:wake never ran, or last ran more than five minutes ago.Schedule it every minute with withoutOverlapping()->onOneServer() and run the scheduler.
step typesok / failThe catalogue fails to load, or an active version uses a step type that is no longer registered.Fix or remove the named class, register the type again, or publish a version without it.
config schemafailA host type’s configSchema() uses a keyword the validator doesn’t support.Write it with ConfigSchema.
adapterfailAn active definition uses a step type whose package is missing.composer require roundly-consulting/<package>, or publish a version without the step.
calendarfailAn active definition’s default calendar is missing.Register it, or create the opening-hours calendar for the owner.
opening hoursfailAn active definition uses an opening_hours: calendar but its owner model doesn’t use HasOpeningHours.Add implements OpeningHoursOwner + use HasOpeningHours to the owner model (for teams: your swapped team model).
condition waitswarnAn active version waits on a condition of a subject type whose model doesn’t use HasWorkflows.Add the trait, or accept that it re-checks on its poll only.
eventsfail / warnA mapped class doesn’t exist or '*' isn’t a WorkflowEvent (fail); a mapped event carries no idempotency key, a class is mapped twice, or an on_behalf_of event definition’s mapping has no user (warn).Fix the mapping; add idempotency_key; keep one mapping; map user or run as system.
webhooksok / failThe route is enabled without verify_middleware (it stays unregistered).Set workflows.webhooks.verify_middleware, e.g. ['sentinel.signed:<profile>'].
stuckwarnRunning instances with nothing queued, running or waiting for longer than engine.lease.workflows:show <ulid>; usually a lost job that wake re-pushes after engine.requeue_after; otherwise workflows:retry or workflows:cancel.
wake backlogwarnWaits or escalations due more than five minutes ago.Check the scheduler and raise wake.batch.
bridges / failuresokThe installed bridges; how many instances failed in the last 24 hours.—

Failure codes

WorkflowInstance::failure() and StepRun::error() return the FailureCode case (a code:detail code by its prefix; FailureCode::detail() reads the detail) or, for a host step’s own code, the string. “Retry helps” means workflows:retry (or instances()->retry()) can succeed once the cause is gone — FailureCode::retryable(). The retry picks up where the step failed: a failed handle() runs again, and a failed resume() is resumed again with the signal it received (the event’s payload, the decision, the timeout), so an event that already arrived does not have to be sent again. Every code has an English and a Slovak message (FailureCode::message()).

CodeWritten whenRetry helps
adapter_missingA step needs a package that isn’t installed (approval, notify, transition, a form trigger).yes, after installing it
approval_cancelledThe approval round was cancelled outside the workflow.no
approval_closedThe approval round was already closed when the step acted on it.no
approval_missingThe approval round no longer exists.no
authorization_deniedThe authorization hook refused the step (policy, abilities, the system actor).yes, after granting it
calendar_missingA business calendar the step needs is missing for the owner.yes, after creating it
calendar_never_openThe calendar has no open time within the search window.no (fix the calendar, then cancel and restart)
child_not_startedA start_workflow child could not be started (retired, inactive, inputs not covered), or the start-rate limit refused it (retried automatically with backoff).yes, after fixing the child definition (or once the rate window has room)
delay_field_emptyA delay with until_field found the field empty.yes, after filling the field
ended_by_graphAn end node with status: failed.no
exceptionA handler threw an unknown exception (its message is stored for operators, never rendered).yes
field_write_refusedset_field targeted a lifecycle state column.no
idempotency_conflictThe lifecycle saw the same idempotency key for a different transition.no
invalid_approval_requestApprovals refused the request (configuration).no
invalid_outputA handler returned output that isn’t plain JSON or is over steps_output.max_bytes.no (fix the handler)
invalid_valueset_field got a value of the wrong type at run time.no
lease_expiredA claimed step outlived its lease (a crashed or slow worker).yes (retried automatically)
missing_edgeThe next node does not exist.no
missing_outcome_edgeA step ended with an outcome that has no edge.no (publish a version with the edge)
missing_triggerThe version has no trigger.no
nesting_depth_exceededstart_workflow went deeper than limits.max_depth.no
no_approversThe approvers resolved to no one.yes, after fixing the parties
no_resultA handler returned nothing.no (fix the handler)
notification_refused:<code>Notifications refused the notification permanently.no
owner_mismatchThe subject no longer belongs to the workflow’s owner.yes, if it moves back
resume_unsupportedA signal reached a step type that cannot resume.no
roles_unavailableA role party but no roles resolver (teams or parties.roles).yes, after configuring one
start_rate_exceededThe definition started more than limits.max_starts_per_minute times in a minute.no — no step ran; send the start again (its key was left free)
step_limit_exceededThe instance ran more than limits.max_steps steps.no
step_missingAn escalation’s waiting step no longer exists.no
subject_missingThe subject was deleted or is invisible in the owner context.yes, if it is restored
transition_conflictAnother change reached the subject first (retried automatically).yes
transition_denied:<code>The lifecycle guard refused the transition (retried automatically only when the lifecycle says so).no — unless what the guard checks changes
transition_unusableThe transition can’t be used on this subject.no
unknown_approval_presetThe approval preset does not exist.no
unknown_escalation_actionAn escalation action type is not available.no
unknown_step_typeThe node’s step type is not registered (or the node is gone).yes, after registering it
version_missingThe instance’s version no longer exists.no
worker_timeoutThe queue worker killed the job.yes (retried automatically)

Package exceptions that a step turns into a failure (a child start refused, for example) keep their own error codes — see Exceptions.

Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

More ways to support, including crypto

By donating, you agree to our donation terms.

Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.