Link automation.action.* events to automation.triggered event#156
Conversation
When debugging automation failures at high volumes, it's difficult to trace from an `automation.action.failed` event back to the `automation.triggered` event that spawned it. This adds an `automation_triggered_event_id` field to `TriggeredAction` and includes it as a related resource with role `automation-triggered-event` on all action events (`.triggered`, `.executed`, and `.failed`). Port of https://github.com/PrefectHQ/nebula/pull/10911 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The triggering-event related resource should appear on all three action event types (.triggered, .executed, .failed) to enable full traceability. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Now that automation.action.executed and automation.action.failed events include a triggering-event related resource, update the tests to expect it in their assertions. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Build related_resources once at the start of fail() and succeed() methods and reuse it for both the .triggered and .failed/.executed events, rather than building separate lists with identical logic. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add a brief section to the automations docs explaining the events emitted during automation execution and the related resources (triggering-event and automation-triggered-event) available for tracing action failures back to their source. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Code Review by Qodo
1. triggers.py missing future annotations
|
| # Determine the primary state change event ID for linking action events back to | ||
| # the automation.triggered or automation.resolved event. Prefer Triggered over | ||
| # Resolved when both are present. | ||
| primary_state_change_event = state_change_events.get( | ||
| TriggerState.Triggered | ||
| ) or state_change_events.get(TriggerState.Resolved) | ||
| primary_state_change_event_id = ( | ||
| primary_state_change_event.id if primary_state_change_event else None | ||
| ) | ||
|
|
||
| # By default, all `automation.actions` are fired. Each tuple contains: | ||
| # (triggering_event, action, automation_triggered_event_id) | ||
| source_actions: List[ | ||
| Tuple[Optional[ReceivedEvent], ServerActionTypes, UUID | None] | ||
| ] = [ | ||
| (firing.triggering_event, action, primary_state_change_event_id) | ||
| for action in automation.actions | ||
| ] |
There was a problem hiding this comment.
1. triggers.py missing future annotations 📘 Rule violation ✓ Correctness
• src/prefect/server/events/triggers.py contains type annotations (including PEP604 unions like UUID | None) but does not include from __future__ import annotations as the first import. • This violates the repository rule for consistent forward-reference behavior and type-checking performance across src/ Python files. • It can also create subtle typing/runtime differences compared to other modules that follow the required pattern.
Agent prompt
## Issue description
`src/prefect/server/events/triggers.py` uses type annotations but does not include `from __future__ import annotations` as the first import, violating the required typing convention for `src/`.
## Issue Context
The repository compliance rule mandates this import in any `src/` Python module that contains type annotations.
## Fix Focus Areas
- src/prefect/server/events/triggers.py[1-24]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| @field_validator("automation_triggered_event_id") | ||
| @classmethod | ||
| def validate_automation_triggered_event_id(cls, v, info): | ||
| """Ensure automation_triggered_event_id is only set when triggering_event exists.""" | ||
| if v is not None and info.data.get("triggering_event") is None: | ||
| raise ValueError( | ||
| "automation_triggered_event_id can only be set when triggering_event is provided" | ||
| ) | ||
| return v |
There was a problem hiding this comment.
2. automation_triggered_event_id uses field_validator 📘 Rule violation ✓ Correctness
• TriggeredAction enforces a cross-field constraint (automation_triggered_event_id depends on triggering_event) via @field_validator, which is not the approved approach for cross-field validation. • This violates the requirement to use @model_validator for validations that depend on multiple fields. • Keeping cross-field logic in a field validator can lead to order-dependent behavior and missed/incorrect validation during model construction.
Agent prompt
## Issue description
`TriggeredAction` performs cross-field validation using `@field_validator`, which violates the requirement to use `@model_validator` for logic that depends on multiple fields.
## Issue Context
The validator checks `triggering_event` while validating `automation_triggered_event_id`, which is cross-field validation.
## Fix Focus Areas
- src/prefect/server/events/schemas/automations.py[763-780]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| # By default, all `automation.actions` are fired. Each tuple contains: | ||
| # (triggering_event, action, automation_triggered_event_id) | ||
| source_actions: List[ | ||
| Tuple[Optional[ReceivedEvent], ServerActionTypes, UUID | None] | ||
| ] = [ | ||
| (firing.triggering_event, action, primary_state_change_event_id) | ||
| for action in automation.actions | ||
| ] |
There was a problem hiding this comment.
3. automation_triggered_event_id set with none triggering_event 📘 Rule violation ⛯ Reliability
• act() always passes a non-null primary_state_change_event_id into TriggeredAction.automation_triggered_event_id even when triggering_event is None (e.g., proactive triggers). • With the newly added validation, this can raise ValueError and break action publishing instead of handling the None edge case gracefully. • This violates the robustness requirement to explicitly handle null/empty edge cases at potential failure points.
Agent prompt
## Issue description
`act()` can construct `TriggeredAction` objects with `triggering_event=None` while still setting `automation_triggered_event_id`, which (with the new validator) raises an exception instead of gracefully handling this edge case.
## Issue Context
This can happen for proactive triggers where there is no external triggering event, but state-change events still exist.
## Fix Focus Areas
- src/prefect/server/events/triggers.py[439-489]
- src/prefect/server/events/schemas/automations.py[763-780]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| occurred=triggered_action.triggered, | ||
| event="prefect.automation.action.triggered", | ||
| resource=resource, | ||
| related=related_resources, | ||
| related=self._resulting_related_resources, | ||
| payload=action_details, | ||
| id=triggered_event_id, | ||
| follows=follows_id, |
There was a problem hiding this comment.
4. action.triggered missing related links 📘 Rule violation ✧ Quality
• In the failure path, fail() builds related_resources including automation-triggered-event and triggering-event, but then emits the prefect.automation.action.triggered event with related=self._resulting_related_resources instead of the enriched related_resources. • This prevents consumers from reliably tracing the full causal chain for failed actions at the moment the action was triggered, weakening audit/trace reconstruction. • This violates the audit trail requirement to log critical actions with sufficient context to reconstruct events.
Agent prompt
## Issue description
In `fail()`, the enriched `related_resources` is constructed but not used for the emitted `prefect.automation.action.triggered` event, dropping traceability context.
## Issue Context
This reduces audit/trace reconstruction quality for failed actions because the triggered event lacks the expected related links.
## Fix Focus Areas
- src/prefect/server/events/actions.py[177-206]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
Benchmark PR from agentic-review-benchmarks#2