Skip to content

Link automation.action.* events to automation.triggered event#156

Open
tomerqodo wants to merge 7 commits intoqodo_action_req_1_base_link_automationaction_events_to_automationtriggered_event_pr2from
qodo_action_req_1_head_link_automationaction_events_to_automationtriggered_event_pr2
Open

Link automation.action.* events to automation.triggered event#156
tomerqodo wants to merge 7 commits intoqodo_action_req_1_base_link_automationaction_events_to_automationtriggered_event_pr2from
qodo_action_req_1_head_link_automationaction_events_to_automationtriggered_event_pr2

Conversation

@tomerqodo
Copy link
Copy Markdown

Benchmark PR from agentic-review-benchmarks#2

chrisguidry and others added 7 commits January 25, 2026 12:09
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>
@github-actions github-actions Bot added the docs label Jan 29, 2026
@qodo-code-review
Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (4) 📎 Requirement gaps (0)

Grey Divider


Action required

1. triggers.py missing future annotations 📘 Rule violation ✓ Correctness
Description
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.
Code

src/prefect/server/events/triggers.py[R439-456]

+    # 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
    ]
Evidence
The compliance rule requires from __future__ import annotations at the top of any src/ Python
file that uses type hints. The file uses annotated variables/types in the changed hunk, and the file
header shows the import is missing.

AGENTS.md
src/prefect/server/events/triggers.py[1-24]
src/prefect/server/events/triggers.py[439-456]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## 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


2. automation_triggered_event_id uses field_validator 📘 Rule violation ✓ Correctness
Description
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.
Code

src/prefect/server/events/schemas/automations.py[R772-780]

+    @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
Evidence
The checklist explicitly requires cross-field validation to be implemented with @model_validator.
The added validator checks info.data.get("triggering_event"), meaning it depends on another field,
but is implemented as a @field_validator.

AGENTS.md
src/prefect/server/events/schemas/automations.py[763-780]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## 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


3. automation_triggered_event_id set with None triggering_event 📘 Rule violation ⛯ Reliability
Description
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.
Code

src/prefect/server/events/triggers.py[R449-456]

+    # 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
    ]
Evidence
The act() change constructs tuples where the first element is Optional[ReceivedEvent] (can be
None) while the third element is the state-change event id (typically present). The new validator
rejects any non-null automation_triggered_event_id when triggering_event is absent, creating an
unhandled None edge case leading to a raised exception.

Rule 3: Generic: Robust Error Handling and Edge Case Management
src/prefect/server/events/triggers.py[449-456]
src/prefect/server/events/schemas/automations.py[772-780]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## 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


View more (1)
4. action.triggered missing related links 📘 Rule violation ✧ Quality
Description
• 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.
Code

src/prefect/server/events/actions.py[R199-205]

                    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,
Evidence
The compliance rule requires audit records to include essential context to reconstruct what
happened. The code clearly constructs an enriched related_resources list, but the emitted
prefect.automation.action.triggered event does not use it, dropping the linking context needed for
tracing.

Rule 1: Generic: Comprehensive Audit Trails
src/prefect/server/events/actions.py[177-206]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## 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



ⓘ The new review experience is currently in Beta. Learn more

Qodo Logo

Comment on lines +439 to 456
# 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
]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

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

Comment on lines +772 to +780
@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
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

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

Comment on lines +449 to 456
# 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
]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

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

Comment on lines 199 to 205
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,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants