Guilherme Henrique Campos Botelho
05/29/2026, 10:04 PMMarvin
05/29/2026, 10:04 PMMarvin
05/29/2026, 10:07 PMfor_each, including labels from related resources.
For your case, the best grouping key is:
json
"for_each": ["related:flow:prefect.resource.id"]
If you want one alert per deployment instead, use:
json
"for_each": ["related:deployment:prefect.resource.id"]
A few important details:
* prefect.resource.name groups by the flow run resource itself, so that will still be effectively per run.
* related:flow:prefect.resource.name is valid syntax, but grouping by name can collide if multiple flows share the same name.
* related:flow:prefect.resource.id is the safest/recommended choice because it groups by the unique flow resource.
So the answer is: yes, grouping by flow or deployment is supported, and the correct for_each value is usually one of these:
json
["related:flow:prefect.resource.id"]
or
json
["related:deployment:prefect.resource.id"]
Your trigger would look like:
json
{
"type": "event",
"match": { "prefect.resource.id": "prefect.flow-run.*" },
"match_related": { "prefect.resource.role": "deployment" },
"after": ["prefect.flow-run.AwaitingConcurrencySlot"],
"expect": [
"prefect.flow-run.Running",
"prefect.flow-run.Cancelled",
"prefect.flow-run.Crashed",
"prefect.flow-run.Failed",
"prefect.flow-run.Completed"
],
"for_each": ["related:flow:prefect.resource.id"],
"posture": "Proactive",
"threshold": 1,
"within": 7200
}
One caveat: this gives you one firing per grouped bucket per evaluation window, not a permanent dedupe forever. So if a later run for the same flow also gets stuck in a new qualifying window, the automation can fire again.
Relevant docs:
* Event triggers
* Flow run events
If you want, I can also help sanity-check whether your use of both after and expect matches the behavior you want for AwaitingConcurrencySlot.Guilherme Henrique Campos Botelho
06/11/2026, 2:36 AMMarvin
06/11/2026, 2:39 AMMarvin
06/11/2026, 2:42 AMAwaitingConcurrencySlot and does not transition to a terminal/running state within 2 hours.”
The main thing to be careful about is your for_each choice:
yaml
for_each:
- "related:deployment:prefect.resource.id"
That means the trigger is evaluated per deployment, not per flow run. So if your goal is “only one alert per deployment even if multiple runs are stuck,” this is the right grouping key.
If instead you wanted one alert per flow, use:
yaml
for_each:
- "related:flow:prefect.resource.id"
If you wanted one alert per run, use:
yaml
for_each:
- "prefect.resource.id"
So your current config is correct if deployment-level grouping is what you want.
I’d write it as:
yaml
trigger:
type: event
match:
prefect.resource.id: "prefect.flow-run.*"
match_related:
prefect.resource.role: deployment
posture: Proactive
after:
- prefect.flow-run.AwaitingConcurrencySlot
expect:
- prefect.flow-run.Running
- prefect.flow-run.Cancelled
- prefect.flow-run.Crashed
- prefect.flow-run.Failed
- prefect.flow-run.Completed
for_each:
- "related:deployment:prefect.resource.id"
threshold: 1
within: 7200
Docs:
* Event triggers
* Detect zombie flows
2. call-webhook to Discord
Yes — this is the right native approach if you’re not using a dedicated Discord integration.
Using a Webhook block pointing at the Discord webhook URL with:
yaml
actions:
- type: call-webhook
block_document_id: ...
payload: '{"content":"..."}'
is the expected pattern.
Discord accepts JSON payloads like:
json
{"content":"message text"}
So having Content-Type: application/json on the block is appropriate.
Related docs:
* Custom notifications
3. Are {{ flow_run.name }} variables available?
Yes — {{ flow_run.name }} is available in automation templating for event-triggered actions.
So this should work:
yaml
payload: '{"content": ":warning: {{ flow_run.name }} has been awaiting a concurrency slot for over 2 hours."}'
You can also use other objects like flow, deployment, and event, plus useful filters like ui_url.
For example:
yaml
payload: |
{
"content": ":warning: Flow run {{ flow_run.name }} from deployment {{ deployment.name }} has been awaiting a concurrency slot for over 2 hours. {{ flow_run | ui_url }}"
}
4. Referencing a block by name in automation YAML
As far as the current schema goes, there is not a native YAML field to reference the webhook block by slug/name instead of UUID.
call-webhook expects:
yaml
block_document_id: <uuid>
So resolving the block document ID at deploy time by name/slug is a reasonable workaround.
I’m not aware of a more native automation-YAML option like:
yaml
block: webhook/my-discord-block
for call-webhook.
If you want, the most stable pattern is:
* keep the Webhook block name stable
* resolve its document ID during your deployment/build step
* inject that UUID into the generated automation YAML
Bottom line
* Yes, your trigger pattern is valid.
* Yes, call-webhook + Webhook block is the right Discord path.
* Yes, {{ flow_run.name }} should work.
* No, there isn’t currently a better native block-by-name reference in automation YAML; UUID resolution is the normal workaround.
If you want, I can help you turn this into a complete working automation YAML example for Discord, including a safer escaped payload.Guilherme Henrique Campos Botelho
06/11/2026, 2:47 AMMarvin
06/11/2026, 2:47 AMGuilherme Henrique Campos Botelho
06/11/2026, 2:48 AMMarvin
06/11/2026, 2:48 AMMarvin
06/11/2026, 2:50 AMsend-notification + DiscordWebhook
Valid and supported. You do not need to go back to call-webhook + Webhook if you already have a DiscordWebhook block.
In Prefect 3.x:
* send-notification is meant for notification blocks
* DiscordWebhook is a notification block
* subject and body are valid fields on the automation action
So this is a good fit:
yaml
actions:
- type: send-notification
block_document_id: "$block:discord-webhook/discord-concurrency-alert"
subject: "Flow travado em AwaitingConcurrencySlot"
body: ":warning: ..."
A few practical notes:
* Discord typically cares more about the message body than the subject.
* Depending on how the Discord notification block formats messages internally, the subject may be ignored or folded into the message text.
* If the visible output in Discord matters, put the essential content in body, not only in subject.
So: your current approach is cleaner and more native than call-webhook.
2. client.read_block_document_by_name(name, block_type_slug)
Yes — this is the correct and stable API to resolve a block document by name at deploy time.
Your usage pattern is reasonable:
python
block_doc = await client.read_block_document_by_name(block_name, block_type)
That method is part of the public orchestration client interface and is the right tool for:
* block name known ahead of time
* needing the document UUID for automation specs
* deploy-time resolution
A couple of small recommendations:
* block_type should be the slug (discord-webhook), which you’re already using.
* Consider handling the “not found” case explicitly so failures are clearer.
* If you don’t need secret values from the block document, you can pass include_secrets=False.
For example:
python
block_doc = await client.read_block_document_by_name(
block_name,
block_type,
include_secrets=False,
)
That said, your current code is fine for ID resolution.
Overall verdict
* `send-notification + DiscordWebhook`: yes, valid; good choice
* `read_block_document_by_name(...)`: yes, correct/stable for deploy-time lookup
Your $block:... preprocessing approach is also a sensible abstraction since automation YAML itself does not natively resolve block references by slug/name.
If you want, I can suggest a couple of small hardening improvements to these scripts, like:
* validating the Discord webhook URL format
* better exception handling for missing blocks
* avoiding repeated client lookups when many automations reference the same blockGuilherme Henrique Campos Botelho
06/11/2026, 2:53 AMMarvin
06/11/2026, 2:53 AMMarvin
06/11/2026, 2:54 AMactions[].
In Prefect 3.x, block_document_id appears only on action objects, specifically actions like:
* send-notification
* call-webhook
But automation specs can store actions in three top-level fields:
yaml
actions:
actions_on_trigger:
actions_on_resolve:
So the important distinction is:
* Yes: block_document_id is only on actions
* No: those actions are not guaranteed to live only in spec["actions"]
I did not find block_document_id in:
* trigger definitions
* trigger conditions
* match / match_related
* nested trigger structures
* arbitrary other parts of the automation spec tree
Also, there don’t appear to be nested action trees where one action contains child actions. So you do not need a fully recursive traversal of the entire spec if you handle all three action lists.
A good simplified version would be something like:
python
async def resolve_block_refs(spec: dict) -> dict:
from prefect.client.orchestration import get_client
action_fields = ["actions", "actions_on_trigger", "actions_on_resolve"]
refs = {
action["block_document_id"]
for field in action_fields
for action in spec.get(field, [])
if isinstance(action.get("block_document_id"), str)
and action["block_document_id"].startswith("$block:")
}
if not refs:
return spec
ref_to_uuid = {}
async with get_client() as client:
for ref in refs:
_, path = ref.split(":", 1)
block_type, block_name = path.split("/", 1)
block_doc = await client.read_block_document_by_name(
block_name, block_type, include_secrets=False
)
ref_to_uuid[ref] = str(block_doc.id)
for field in action_fields:
for action in spec.get(field, []):
if action.get("block_document_id") in ref_to_uuid:
action["block_document_id"] = ref_to_uuid[action["block_document_id"]]
return spec
So my recommendation:
* If you only support actions, your simplified version is not fully safe
* If you support actions, actions_on_trigger, and actions_on_resolve, that should be sufficient
* Full recursive traversal of the whole spec tree is probably more general than necessary
So the middle ground is best: targeted traversal over the known action lists.
If you want, I can also help refactor this into a cleaner helper with validation for malformed $block:type/name references.