Project: Prefect (Orchestration & Management / Scheduling & Orchestration)
File: state-change-hooks.md

description: Execute code in response to a flow or task entering a given state, without involvement of the Prefect API. tags: state change hooks triggers search: boost: 2 execute code in response to changes in flow or task run states, enabling you to define actions for specific state transitions in a workflow. This guide provides examples of real-world use cases. State change hooks enable you to customize messages sent when tasks transition between states, such as sending notifications containing sensitive information when tasks enter a `Failed` state. Here's an example of running a client-side hook upon a flow run entering a `Failed` state: ```python from prefect import flow from prefect.blocks.core import Block from prefect.settings import PREFECTAPIURL def notifyslack(flow, flowrun, state): slackwebhookblock = Block.load( "slack-webhook/my-slack-webhook" ) slackwebhookblock.notify( ( f"Your job {flow_run.name} entered {state.name} " f"with message:\n\n" f"See <https://{PREFECTAPIURL.value()}/flow-runs/" f"flow-run/{flow_run.id}|the flow run in the UI>\n\n" f"Tags: {flow_run.tags}\n\n" f"Scheduled start: {flowrun.expectedstart_time}" ) ) @flow(onfailure=[notifyslack], retries=1) def failing_flow(): raise ValueError("oops!") if name == "main": failing_flow() ``` Note that retries are configured in this example. This means the `on_failure` hook will not run until all `retries` have completed when the flow run enters a `Failed` state. State change hooks help manage infrastructure cleanup in scenarios where tasks spin up individual infrastructure resources independently of Prefect. When a flow run crashes, tasks may exit abruptly and result in the potential omission of cleanup logic within the tasks. Use state change hooks to ensure infrastructure is properly cleaned up, even when a flow run enters a `Crashed` state. Here's how to create a hook that deletes a Cloud Run job if the flow run crashes: ```python import os from prefect import flow, task from prefect.blocks.system import String from prefect.client import get_client import prefect.runtime async def deletecloudrunjob(flow, flowrun, state): """Flow run state change hook that deletes a Cloud Run Job if the flow run crashes.""" cloudrunjob_name = await String.load( name="crashing-flow-cloud-run-job" ) deletejobcommand = f"yes | gcloud beta run jobs delete {cloudrunjob_name.value} --region us-central1" os.system(deletejobcommand) async with get_client() as client: blockdocument = await client.readblockdocumentby_name( "crashing-flow-cloud-run-job", blocktypeslug="string" ) await client.deleteblockdocument(block_document.id) @task def mytaskthat_crashes(): raise SystemExit("Crashing on purpose!") @flow(oncrashed=[deletecloudrunjob]) def crashing_flow(): """Save the flow run name (i.e. Cloud Run job name) as a String block. It then executes a task that ends up crashing.""" flowrunname = prefect.runtime.flow_run.name cloudrunjobname = String(value=flowrun_name) cloudrunjob_name.save( name="crashing-flow-cloud-run-job", overwrite=True ) mytaskthat_crashes() if name == "main": crashing_flow() ```