This walkthrough documents how generated AshJido actions fail and what callers should expect.
1. Error Mapping
AshJido maps Ash failures to Jido action errors.
| Failure Source | Typical Jido Error | Notes |
|---|---|---|
Validation/input failures (Ash.Error.Invalid) | Jido.Action.Error.InvalidInputError | Field-level details are preserved in error.details |
Authorization failures (Ash.Error.Forbidden) | Jido.Action.Error.ExecutionFailureError | error.details.reason is commonly :forbidden |
| Runtime/framework/unknown failures | Jido.Action.Error.InternalError | Includes wrapped Ash/context error details |
2. Deterministic Failure Cases
Missing domain
Generated actions resolve the Ash domain from context[:domain] first, then from the
resource's static domain: configuration. They raise ArgumentError only when both
are missing.
assert_raise ArgumentError, ~r/:domain must be provided/, fn ->
MyApp.Accounts.User.Jido.Read.run(%{}, %{})
endMissing primary key for update/destroy
assert {:error, %Jido.Action.Error.ExecutionFailureError{} = error} =
MyApp.Content.Post.Jido.Update.run(
%{title: "missing primary key"},
%{domain: MyApp.Content}
)
error.message # => "Update actions require an 'id' parameter"Resources with a non-id or composite primary key report the generated primary key
fields instead, e.g. "Update actions require primary key parameter(s): account_id, external_id".
Destroy actions also validate declared Ash destroy action arguments, so missing or invalid destroy arguments surface as regular Ash/Jido validation errors.
Missing signal_dispatch when signaling is enabled
assert {:error, %Jido.Action.Error.InvalidInputError{} = error} =
MyApp.Content.Post.Jido.Create.run(
%{title: "Missing dispatch", author_id: author_id},
%{domain: MyApp.Content}
)
String.contains?(error.message, "signal dispatch configuration is required")Dispatch failure after successful Ash operation
When signal dispatch fails after a successful write, the action result remains successful.
missing_named_dispatch =
{:named, [target: {:name, :missing_target}, delivery_mode: :sync]}
assert {:ok, created} =
MyApp.Content.Post.Jido.Create.run(
%{title: "Dispatch Failure", author_id: author_id},
%{domain: MyApp.Content, signal_dispatch: missing_named_dispatch}
)
created[:title] # => "Dispatch Failure"3. Telemetry for Failures
With telemetry?: true, use stop/exception metadata to distinguish outcomes.
Mapped error path (no exception)
[:jido, :action, :ash_jido, :start][:jido, :action, :ash_jido, :stop]withresult_status: :error
Exception path
[:jido, :action, :ash_jido, :start][:jido, :action, :ash_jido, :exception]with:result_status: :errorerror_kinderror_reasonerror_stacktrace
Signal delivery metadata
Stop metadata includes signal outcome counters:
signal_sent_countsignal_failed_countsignal_failures(when failures occur)
These let you keep write success semantics while monitoring downstream dispatch health.