Russell Brooks
05/27/2026, 10:27 AMMarvin
05/27/2026, 10:27 AMMarvin
05/27/2026, 10:29 AMautomations.yaml example for Prefect 3.x that notifies when a flow run fails, crashes, or times out, limited to flow runs associated with either the default or backup work pool.
yaml
automations:
- name: notify-on-flow-run-failure-crash-timeout
description: Notify when a flow run fails, crashes, or times out in default or backup work pools
enabled: true
trigger:
type: event
posture: Reactive
expect:
- prefect.flow-run.Failed
- prefect.flow-run.Crashed
- prefect.flow-run.TimedOut
threshold: 1
match:
prefect.resource.id: prefect.flow-run.*
match_related:
- prefect.resource.role: work-pool
prefect.resource.name:
- default
- backup
actions:
- type: send-notification
block_document_id: REPLACE_ME_WITH_YQ
subject: >-
{% if event.event == "prefect.flow-run.Failed" -%}
Flow run FAILED: {{ flow_run.name }}
{%- elif event.event == "prefect.flow-run.Crashed" -%}
Flow run CRASHED: {{ flow_run.name }}
{%- elif event.event == "prefect.flow-run.TimedOut" -%}
Flow run TIMED OUT: {{ flow_run.name }}
{%- else -%}
Flow run issue: {{ flow_run.name }}
{%- endif %}
body: |-
{% if event.event == "prefect.flow-run.Failed" -%}
A flow run *failed*.
{%- elif event.event == "prefect.flow-run.Crashed" -%}
A flow run *crashed*.
{%- elif event.event == "prefect.flow-run.TimedOut" -%}
A flow run *timed out*.
{%- else -%}
A flow run encountered a problem.
{%- endif %}
*Flow run:* {{ flow_run.name }}
*Flow run ID:* {{ flow_run.id }}
*Flow:* {{ flow.name }}
*State:* {{ flow_run.state.name }}
*Message:* {{ flow_run.state.message or "No state message" }}
*Work pool:* {{ work_pool.name if work_pool else "Unknown" }}
*Deployment:* {{ deployment.name if deployment else "Ad hoc / unknown" }}
*UI:* {{ flow_run | ui_url }}
Best way to keep this current if the work pool ID changes:
Use work pool names, not IDs, in match_related, exactly like above:
yaml
match_related:
- prefect.resource.role: work-pool
prefect.resource.name:
- default
- backup
That way, if the underlying work pool UUID/hash changes over time, the automation still matches as long as the pool names remain default and backup.
If you instead matched on:
yaml
prefect.resource.id: prefect.work-pool.<uuid>
then you’d need to update the automation whenever that ID changes.
So the practical answer is:
Use prefect.resource.name for work pools if you want the automation to stay current across work pool ID changes.
Relevant docs:
- Automations concepts
- Create automations
If you want, I can also give you:
- a version formatted specifically for prefect deploy consumption, or
- a yq command snippet to patch in the block_document_id.Russell Brooks
05/27/2026, 10:38 AMMarvin
05/27/2026, 10:38 AMMarvin
05/27/2026, 10:38 AMFailed, Crashed, or TimedOut.
Why:
- type: event
- posture: Reactive
- threshold: 1
means: trigger immediately when one matching event is observed.
So this part is sufficient:
yaml
trigger:
type: event
posture: Reactive
expect:
- prefect.flow-run.Failed
- prefect.flow-run.Crashed
- prefect.flow-run.TimedOut
threshold: 1
match:
prefect.resource.id: prefect.flow-run.*
match_related:
- prefect.resource.role: work-pool
prefect.resource.name:
- default
- backup
You’d only add a time window like within if you wanted behavior such as:
- “2 failures within 10 minutes”
- “fire only if something does not happen within X time”
- “deduplicate/group events over a period”
For your use case, no extra time field is needed.
If you want, I can also sanity-check the full YAML structure against Prefect’s expected automations.yaml format.Russell Brooks
05/27/2026, 10:57 AMMarvin
05/27/2026, 10:57 AMMarvin
05/27/2026, 10:58 AMflow_run.state.message, so the best you can do in-template is truncate that message or extract a small leading portion.
A simple approach is to show only the first couple of lines of the state message:
yaml
body: |-
{% if event.event == "prefect.flow-run.Failed" -%}
A flow run *failed*.
{%- elif event.event == "prefect.flow-run.Crashed" -%}
A flow run *crashed*.
{%- elif event.event == "prefect.flow-run.TimedOut" -%}
A flow run *timed out*.
{%- else -%}
A flow run encountered a problem.
{%- endif %}
*Flow run:* {{ flow_run.name }}
*Flow run ID:* {{ flow_run.id }}
*Flow:* {{ flow.name }}
*State:* {{ flow_run.state.name }}
*Main error excerpt:*
{{ (flow_run.state.message or "No state message").split('\n')[:3] | join('\n') }}
*Work pool:* {{ work_pool.name if work_pool else "Unknown" }}
*Deployment:* {{ deployment.name if deployment else "Ad hoc / unknown" }}
*UI:* {{ flow_run | ui_url }}
That will print only the first 3 lines of the message.
If you want it even shorter, use just 1–2 lines:
yaml
{{ (flow_run.state.message or "No state message").split('\n')[:2] | join('\n') }}
If instead you want to limit by character count rather than lines:
yaml
{{ (flow_run.state.message or "No state message")[:300] }}...
A couple caveats:
- For some failures, the most useful error text is at the top of `state.message`; for others, the exception summary may be at the bottom.
- If the traceback is not present in flow_run.state.message, the template won’t be able to pull it from logs automatically.
If you want, I can rewrite your full body: block with a clean “short error excerpt” section included.Russell Brooks
05/27/2026, 11:08 AMMarvin
05/27/2026, 11:08 AMMarvin
05/27/2026, 11:09 AMor.
For your case:
yaml
{% if event.event == "prefect.flow-run.Failed" or event.event == "prefect.flow-run.Crashed" %}
*State message:* {{ flow_run.state.message or "No state message" }}
{% endif %}
If you want it a little cleaner, use in instead:
yaml
{% if event.event in ["prefect.flow-run.Failed", "prefect.flow-run.Crashed"] %}
*State message:* {{ flow_run.state.message or "No state message" }}
{% endif %}
That will show the state message only for Failed or Crashed, and not for Cancelled.
Example in a body block:
yaml
body: |-
*Flow run:* {{ flow_run.name }}
*State:* {{ flow_run.state.name }}
{% if event.event in ["prefect.flow-run.Failed", "prefect.flow-run.Crashed"] %}
*State message:* {{ flow_run.state.message or "No state message" }}
{% endif %}
*UI:* {{ flow_run | ui_url }}
If you also want to include TimedOut, just add it:
yaml
{% if event.event in ["prefect.flow-run.Failed", "prefect.flow-run.Crashed", "prefect.flow-run.TimedOut"] %}
Jinja docs:
- Jinja if statements