Sophia Ponte
11/19/2025, 12:31 AMdef outer_flow():
inner_flow.with_options(
persist_result=True,
on_cancellation=[cancellation_callback]
)
If I cancel the outer flow from the Prefect UI, it seems like the on_cancellation hook for the inner_flow subflow does not run. Why is this? How can I ensure that a subflow's on_cancellation hook runs when it's parent flow is cancelled?Marvin
11/19/2025, 12:31 AMMarvin
11/19/2025, 12:40 AMon_cancellation hooks do not run.
- To guarantee a subflow’s on_cancellation hook runs when its parent is cancelled, run the subflow as its own deployment and call it via run_deployment so it has an independent runner/process. Deployed subflows enter Cancelling first, and their on_cancellation hooks are executed.
Why this happens
- In Prefect 3.x, parent→child cancellation is handled by a server-side cleanup service.
- For subflows that run in the same process as the parent (your case), when the parent is cancelled the server will mark the subflow directly as Cancelled. Prefect only executes on_cancellation hooks when a run transitions to Cancelling; going straight to Cancelled skips those hooks.
- Deployed flow runs (including deployed subflows) are transitioned to Cancelling first; the runner sees that state and executes on_cancellation before final Cancelled.
How to ensure the subflow hook runs
Option A: Make the subflow a deployment and call it by deployment
1) Create a deployment for the inner flow (from your project directory):
prefect deploy ./path/to/file.py:inner_flow -n inner-deployment
2) Call it from the parent via run_deployment so it runs independently:
from prefect import flow
from prefect.deployments import run_deployment
def cancellation_callback(flow, flow_run, state):
print("inner cleanup")
@flow(on_cancellation=[cancellation_callback])
def inner_flow():
...
@flow
def outer_flow():
# Triggers an independent run that will enter Cancelling on parent cancel
run_deployment("inner-flow/inner-deployment")
Notes:
- There can be a short delay (typically up to ~20–30s) between cancelling the parent and the subflow entering Cancelling due to server polling intervals.
Option B: Keep it in-process but don’t rely on on_cancellation
- Put critical cleanup in a finally block inside the subflow so it runs during a graceful shutdown:
from prefect import flow
@flow
def inner_flow():
try:
# work
...
finally:
# best-effort cleanup even if parent is cancelled
...
- Or move the cleanup to the parent’s on_cancellation (if it can safely clean up the child’s resources).
Key takeaways
- on_cancellation hooks only run when a run enters Cancelling.
- In-process subflows go straight to Cancelled on parent cancellation, so their on_cancellation hooks won’t run.
- Use a separate deployment + run_deployment if you need the subflow’s on_cancellation hook to run reliably.Nate
11/19/2025, 1:05 AMSophia Ponte
11/19/2025, 11:09 PMSophia Ponte
11/19/2025, 11:34 PMMarvin
11/19/2025, 11:34 PMMarvin
11/19/2025, 11:38 PMrun_deployment. The subflow will enter Cancelling, its runner will see that state, and the on_cancellation hooks will execute before the final Cancelled state.
1) Create the inner deployment:
prefect deploy ./path/to/file.py:inner_flow -n inner-deployment
2) Call it from the parent:
from prefect import flow
from prefect.deployments import run_deployment
def cancellation_callback(flow, flow_run, state):
print("inner cleanup")
@flow(on_cancellation=[cancellation_callback])
def inner_flow(...):
...
@flow
def outer_flow(...):
# call as an independent run
run_deployment("inner-flow/inner-deployment", parameters={...})
- Note: there can be a short delay (often up to ~20–30s) between cancelling the parent and the subflow entering Cancelling due to server polling intervals.
- If you must keep it in-process: don’t rely on on_cancellation for the inner flow. Put critical cleanup in a finally block (best-effort) or centralize cleanup in the parent’s on_cancellation.
from prefect import flow
@flow
def inner_flow():
try:
...
finally:
# best-effort cleanup even if parent is cancelled
...
If you can share how you’re invoking the inner flow today (direct call vs run_deployment), I can suggest the smallest change to get your hook to fire.Nate
11/20/2025, 8:20 PM