Skip to content

Exceptions Reference ​

EventMachine throws specific exceptions for different error categories. Each exception extends either LogicException (developer errors caught at definition time) or RuntimeException (errors that occur during execution).

Configuration Exceptions ​

Thrown at definition time when machine config is invalid. These are LogicException subclasses — they indicate a bug in your machine definition, not a runtime condition.

InvalidStateConfigException ​

Thrown by StateConfigValidator when machine configuration has structural errors: invalid keys, wrong state types, conflicting options (machine + parallel), malformed transitions, cross-region transitions, etc.

  • Extends: LogicException
  • Thrown from: StateConfigValidator, StateDefinition, MachineDefinition
  • Common causes: Typos in config keys, final states with transitions, parallel states without regions
  • See: Defining States, Writing Transitions

InvalidRouterConfigException ​

Thrown by MachineRouter::register() when endpoint routing options are inconsistent.

  • Extends: LogicException
  • Thrown from: MachineRouter
  • Common causes: only + except together, orphaned machineIdFor/modelFor refs
  • See: Endpoints

InvalidEndpointDefinitionException ​

Thrown when an endpoint definition references undefined events, outputs, or invalid actions. Also thrown for forward endpoint conflicts.

  • Extends: RuntimeException
  • Thrown from: MachineDefinition
  • Common causes: Typo in event type, missing output behavior, forward event collision
  • See: Endpoints

InvalidParallelStateDefinitionException ​

Thrown when parallel state config violates constraints.

  • Extends: LogicException
  • Thrown from: MachineDefinition, StateConfigValidator
  • Common causes: Parallel state without regions, with initial, without persistence, without Machine subclass
  • See: Parallel States

InvalidScheduleDefinitionException ​

Thrown when a schedule references an undefined event type.

  • Extends: RuntimeException
  • Thrown from: MachineDefinition, MachineScheduler
  • See: Scheduled Events

InvalidListenerDefinitionException ​

Thrown when listener config uses removed class-as-key format.

InvalidOutputDefinitionException ​

Thrown when output is defined on a transient or parallel region state.

  • Extends: LogicException
  • Thrown from: StateDefinition
  • See: Outputs

InvalidBehaviorDefinitionException ​

Thrown for malformed behavior tuples: empty, missing class, closure in tuple.

  • Extends: LogicException
  • Thrown from: BehaviorTupleParser

MissingBehaviorParameterException ​

Thrown when a required named parameter is not provided in a behavior tuple.

InvalidMachineClassException ​

Thrown when a job references a machine class that doesn't exist or doesn't extend Machine.

  • Extends: LogicException
  • Thrown from: ChildMachineJob, SendToMachineJob
  • See: Machine Delegation

InvalidJobClassException ​

Thrown when a job actor class doesn't exist or lacks a handle() method.

  • Extends: LogicException
  • Thrown from: ChildJobJob
  • See: Job Actors

InvalidTimerDefinitionException ​

Thrown when a timer duration is zero or negative.

MachineDefinitionNotFoundException ​

Thrown when definition() is not implemented on a Machine subclass.

  • Extends: RuntimeException
  • Thrown from: Machine, MachineScheduler
  • Caught, not surfaced, by: the machine:* commands, which print <Class>: <message> and exit 1. See Resolving the machine argument.

MachineDiscoveryException ​

Thrown when no valid search paths are found for auto-discovering Machine classes.

  • Extends: RuntimeException
  • Thrown from: MachineConfigValidatorCommand

Typed Contract Exceptions ​

These exceptions relate to the typed inter-machine contract system (MachineInput, MachineOutput, MachineFailure).

MachineInputValidationException ​

Thrown when: A MachineInput class cannot be constructed from the parent context. This happens when required constructor parameters are missing from the parent's context or have incompatible types.

HTTP status: 422

When it fires: During child machine creation (sync) or inside ChildMachineJob (async). In async mode, this triggers @fail on the parent.

php
// Parent context has 'orderId' but not 'amount'
'processing_payment' => [
    'machine' => PaymentMachine::class,
    'input'   => PaymentInput::class,  // requires orderId + amount
    '@done'   => 'completed',
    '@fail'   => 'payment_failed',     // MachineInputValidationException routes here
],

Fix: Ensure the parent context contains all keys required by the MachineInput constructor before entering the delegation state.

MachineOutputResolutionException ​

Thrown when: A final state's output key references a MachineOutput class that cannot be resolved. This includes cases where the output class does not exist, is not a valid MachineOutput subclass, or the context does not contain the required constructor parameters.

When it fires: When $machine->output() is called or when ChildMachineCompletionJob resolves the child's output for the parent.

Fix: Verify the MachineOutput class exists, extends MachineOutput, and that the machine's context contains all required constructor parameters at final state entry.

MachineOutputInjectionException ​

Thrown when: An OutputBehavior type-hints a MachineOutput subclass in its __invoke() method, but the child machine does not produce a matching output. This typically occurs in forwarded endpoint outputs that expect a child's typed output.

When it fires: During output behavior parameter resolution in forwarded endpoints.

php
// This output expects PaymentOutput from the child
class CardSubmittedOutput extends OutputBehavior
{
    public function __invoke(ContextManager $context, PaymentOutput $childOutput): array
    {
        // If PaymentMachine doesn't define PaymentOutput, throws MachineOutputInjectionException
    }
}

Fix: Ensure the child machine's final state defines an output that produces the expected MachineOutput type.

MachineFailureResolutionException ​

Thrown when: A delegation state's failure key references a MachineFailure class that cannot be resolved. This includes cases where the class does not exist or is not a valid MachineFailure subclass.

When it fires: During machine definition validation or when @fail attempts to construct the failure instance.

Fix: Verify the MachineFailure class exists and extends MachineFailure.

Query Exceptions ​

InvalidStateQueryException ​

Thrown when Machine::query() receives a state name that cannot be resolved against the machine's definition — not found via exact match, leaf match, or parent match.

  • Extends: InvalidArgumentException
  • Thrown from: MachineQueryBuilder::resolveStateIds()
  • Common causes: Typo in state name, querying a state that was renamed or removed
  • See: Querying Machines

Runtime Exceptions ​

Thrown during machine execution. These indicate conditions that can legitimately occur at runtime.

NoTransitionDefinitionFoundException ​

Thrown when an event has no matching transition in the current state.

  • Extends: LogicException
  • Thrown from: MachineDefinition
  • Caught by: SendToMachineJob (logs warning), TestMachine (assertion helpers)
  • See: Events, Execution Model

UndefinedTargetStateException ​

Thrown when a transition references a target state that doesn't exist in the machine definition.

  • Extends: LogicException
  • Thrown from: TransitionBranch
  • Previously: NoStateDefinitionFoundException

MachineValidationException ​

Thrown when a ValidationGuardBehavior fails. Carries Laravel validation errors. Automatically converted to 422 by endpoint controller.

  • Extends: ValidationException
  • Thrown from: Machine
  • Caught by: MachineController (→ 422), TestMachine (assertion helpers)
  • See: Validation Guards

MachineEventValidationException ​

Thrown when event payload fails validation defined in EventBehavior::rules().

  • Extends: ValidationException
  • Thrown from: EventBehavior
  • See: Events

MachineContextValidationException ​

Thrown when machine context fails validation after action execution.

MaxTransitionDepthExceededException ​

Thrown when recursive transitions (via @always or raised events) exceed the configured depth limit (default: 100).

MachineAlreadyRunningException ​

Thrown when a second event is sent to a machine that's already processing.

  • Extends: RuntimeException
  • Thrown from: Machine
  • Caught by: SendToMachineJob (releases back to queue)
  • See: Execution Model

MachineLockTimeoutException ​

Thrown when lock acquisition times out during parallel dispatch.

  • Extends: RuntimeException
  • Thrown from: MachineLockManager
  • Caught by: Machine (→ MachineAlreadyRunningException), ListenerJob (releases)

NoParentMachineException ​

Thrown when sendToParent() or dispatchToParent() is called on a machine that was not invoked by a parent.

RestoringStateException ​

Thrown when machine state cannot be restored from persisted events.

  • Extends: RuntimeException
  • Thrown from: Machine
  • Caught by: SendToMachineJob (logs warning, discards event)

MissingMachineContextException ​

Thrown when a behavior accesses a context key that doesn't exist.

  • Extends: RuntimeException
  • Thrown from: InvokableBehavior

BehaviorNotFoundException ​

Thrown when a behavior reference cannot be resolved (typo in inline key, invalid behavior type).

  • Extends: RuntimeException
  • Thrown from: ResolvesBehaviors, MachineController

ArchiveException ​

Thrown during event archival/restoration: empty collection, compression failure, decompression failure, invalid data format.

  • Extends: RuntimeException
  • Thrown from: MachineEventArchive, CompressionManager
  • See: Event Archival

Scenario Exceptions ​

ScenarioFailedException ​

Thrown during scenario activation or execution for runtime failures. Renders as 422 JSON response with an actionable error message.

  • Extends: RuntimeException
  • HTTP status: 422 (via render())
  • Thrown from: MachineController, ScenarioPlayer

Factory methods:

MethodWhen
sourceMismatch(expected, actual)Machine is not at the scenario's $source state
eventMismatch(expected, actual)Endpoint's registered event type doesn't match scenario's $event
guardRejection(eventType, currentState, guardClass)A guard rejected during scenario replay
continueEventFailed(state, event, reason)A @continue event was rejected by the machine

ScenarioTargetMismatchException ​

Thrown when the machine did not reach the scenario's $target state after execution completed.

ScenarioConfigurationException ​

Thrown for invalid scenario setup: invalid state route, delegation outcome on non-delegation state, missing properties, machine is faked.

  • Extends: LogicException
  • Thrown from: ScenarioPlayer, MachineScenario
  • See: Scenario Plan

ScenariosDisabledException ​

Thrown when scenario execution is attempted but MACHINE_SCENARIOS_ENABLED is false.

  • Extends: RuntimeException
  • Thrown from: ScenarioPlayer

NoScenarioPathFoundException ​

Thrown when no path exists from source to target.

It carries two distinct findings, and they are not interchangeable. The default case means the states are genuinely not connected by that event. The truncated() factory means the search stopped at its iteration ceiling with work still pending — a path may well exist and simply was not reached. Only the second is fixable, by raising --max-iterations; treating it as the first is how a stopped search gets mistaken for a confident answer.

Neither shipped command lets it escape. ScenarioPathResolver::resolve() raises it, and its only caller is ScenarioValidator::validatePaths(), which catches it and turns both variants into error strings for machine:scenario-validate to print. machine:scenario never triggers it at all — it calls resolveAll(), which does not raise this exception, and reads wasTruncated() instead (resolveAll() can still raise InvalidArgumentException for an unresolvable state name, which the command catches separately). So you meet this exception only by calling resolve() yourself; from either command you get the same distinction as console output.

  • Extends: RuntimeException
  • Thrown from: ScenarioPathResolver

AmbiguousScenarioPathException ​

Thrown by the scaffold command when multiple paths exist from source to target and automatic selection is not possible.

  • Extends: RuntimeException
  • Thrown from: MachineScenarioCommand

Testing Exceptions ​

BehaviorNotFakedException ​

Thrown when asserting on a behavior that was never faked via fake() or spy().

Released under the MIT License.