<@ULVA73B9P> I am using on_completion hooks for my...
# ask-marvin
s
@Marvin I am using on_completion hooks for my task runs. Is there a way to get them to show up in the UI graph view similar to how tasks and flows show up?
m
thought for 113.4 seconds
Short answer: no
on_completion
hooks do not show up as separate nodes in the Prefect 3 UI graph. In Prefect 3.x, the graph view is built from flow runs and task runs. Hooks run inline when the task enters a state like
Completed
, but they are not modeled as their own task/flow run objects, so there’s nothing for the graph to render as a node. A few practical alternatives: 1. Log from the hook If you want visibility in the task run details, logging is the simplest option.
Copy code
from prefect import task
from prefect.logging.loggers import task_run_logger

def completion_hook(task, task_run, state):
    logger = task_run_logger(task_run, task)
    <http://logger.info|logger.info>(f"Completion hook ran for task={task.name} state={state.name}")

@task(on_completion=[completion_hook])
def my_task():
    return 42
2. Emit an event If you want something more observable / automation-friendly, emit a custom event from the hook.
Copy code
from prefect import task
from prefect.events import emit_event

def completion_hook(task, task_run, state):
    emit_event(
        event="my-task.completion-hook-ran",
        resource={"prefect.resource.id": f"prefect.task-run.{task_run.id}"},
        payload={
            "task_name": task.name,
            "state": state.name,
        },
    )

@task(on_completion=[completion_hook])
def my_task():
    return 42
3. Make the hook logic a real task instead If you specifically want graph visibility, the supported pattern is to move that logic into an actual downstream task in the flow. Then it becomes a normal task run and will appear in the graph.
Copy code
from prefect import flow, task

@task
def main_task():
    return 42

@task
def completion_step(result):
    print(f"Post-processing result: {result}")

@flow
def my_flow():
    result = main_task()
    completion_step(result)
That’s the best option if the post-completion logic is important enough that you want: * graph visibility * retries * separate state tracking * caching / concurrency controls * clearer auditability So the rule of thumb is: * use hooks for lightweight side effects * use tasks for anything you want represented in orchestration/UI Relevant docs: * State change hooks * Flows * Tasks * Events If you want, I can also show you how to convert an existing
on_completion
hook pattern into a graph-visible downstream task while preserving the same behavior.