--- title: Interception Hooks description: The full catalog of framework-native interception points and the payload-in/payload-out contract every hook follows mode: "wide" --- Interception hooks give you a single, uniform way to observe and modify CrewAI's runtime at well-defined points — from the moment an execution starts, through every model call, tool call, and task or flow-method step, down to the final output. All points share one contract and one registration API. The four LLM/tool hooks documented in [LLM Hooks](/learn/llm-hooks) and [Tool Hooks](/learn/tool-hooks) are the same mechanism. Their existing decorators (`@before_llm_call`, `@before_tool_call`, ...) and `return False` semantics keep working unchanged; interception hooks generalize the same engine to the rest of the framework. ## The contract Every hook is a **synchronous** callable that receives a single typed context: ```python from crewai.hooks import on, HookAborted, InterceptionPoint @on(InterceptionPoint.INPUT) def add_defaults(ctx): # 1. Observe: read anything off the context. # 2. Mutate in place: change ctx.payload or nested fields directly. ctx.payload.setdefault("locale", "en-US") # 3. Or replace: return a new value to swap ctx.payload. # 4. Or abort: raise HookAborted(reason, source) to stop the operation. return None ``` A hook may do any of four things: | Action | How | Effect | |--------|-----|--------| | **Proceed** | `return None` (or nothing) | Operation continues unchanged | | **Mutate** | Change `ctx.payload` / fields in place | Change is visible downstream | | **Replace** | `return new_payload` | A non-`None` return replaces `ctx.payload` | | **Abort** | `raise HookAborted(reason, source)` | Operation is stopped; the reason propagates | ### Composition, ordering, and fail-open - Multiple hooks on the same point run in **registration order**, global hooks first, then execution-scoped hooks. - The (possibly mutated) payload flows from one hook to the next. - `HookAborted` **propagates by design** and stops the chain. - Any *other* exception raised by a hook is **swallowed** (fail-open) so a single buggy hook can't crash a run — the same protection the legacy hooks provide. - When no hook is registered for a point, dispatch is a single dict lookup (no-op fast path), so unused points cost effectively nothing. ## Registering hooks Use the `@on` decorator for global hooks. It mirrors the legacy decorators' ergonomics, including `agents=` / `tools=` filters: ```python from crewai.hooks import on, InterceptionPoint, HookAborted @on(InterceptionPoint.PRE_TOOL_CALL, tools=["delete_file"]) def guard_deletes(ctx): raise HookAborted(reason="file deletion is not allowed", source="policy") ``` Applied to a method inside a `@CrewBase` class, `@on` registers a crew-scoped hook (active only while that crew runs), matching the existing crew-scoped hook behavior. ## Interception point catalog `payload` is the value a hook may mutate or replace at each point. ### Execution boundaries | Point | When | `payload` | |-------|------|-----------| | `EXECUTION_START` | A crew or flow is about to begin | inputs `dict` | | `INPUT` | Resolved inputs for the execution | inputs `dict` | | `OUTPUT` | Final result is ready | the output object | | `EXECUTION_END` | A crew or flow has finished | the output object | ### Model & tool boundaries (legacy-compatible) | Point | When | `payload` | |-------|------|-----------| | `PRE_MODEL_CALL` | Before an LLM call | `LLMCallHookContext` | | `POST_MODEL_CALL` | After an LLM call | response | | `PRE_TOOL_CALL` | Before a tool runs | `ToolCallHookContext` | | `POST_TOOL_CALL` | After a tool runs | tool result | ### Step points | Point | When | `payload` | |-------|------|-----------| | `PRE_STEP` | Before a task or flow-method step | step input | | `POST_STEP` | After a task or flow-method step | step output | `PRE_STEP` / `POST_STEP` carry `ctx.kind` (`"task"` or `"flow_method"`) and `ctx.step_name`. ## Aborting an operation `HookAborted` carries a `reason` and an optional `source`. The `source` defaults to the aborting hook when omitted, which is useful for telemetry and failure messages: ```python @on(InterceptionPoint.EXECUTION_START) def enforce_policy(ctx): if not ctx.payload.get("authorized"): raise HookAborted(reason="unauthorized execution", source="access-control") ``` ## Telemetry Whenever a point actually dispatches to at least one hook, CrewAI emits a `HookDispatchedEvent` on the event bus with the point, the outcome (`proceeded` / `modified` / `aborted`), the hook count, the duration, and — for aborts — the reason and source. The no-op fast path emits nothing. ## Managing hooks in tests ```python import pytest from crewai.hooks import clear_all_hooks @pytest.fixture(autouse=True) def reset_hooks(): clear_all_hooks() yield clear_all_hooks() ``` ## Related documentation - [Execution Hooks Overview →](/learn/execution-hooks) - [LLM Call Hooks →](/learn/llm-hooks) - [Tool Call Hooks →](/learn/tool-hooks) - [Before and After Kickoff Hooks →](/learn/before-and-after-kickoff-hooks)