> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sqlbuild.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Hooks

> Run validated SQL or Python lifecycle hooks around model materialization.

Pre-hooks and post-hooks run before and after materialization. Each entry is either `sql("...")` or `python("hook_name")`.

## SQL hooks

```sql theme={null}
MODEL (
  materialized table,
  post_hooks [sql('GRANT SELECT ON @@CTX:destination.qualified TO analyst_role')],
);
```

SQL hooks support macros, project variables, environment variables, and context variables. Hook SQL is syntax-validated when the model's effective SQL-validation gate is enabled: SQL analysis must be enabled, `--no-sql-validation` must be absent, and the effective project or model `sql_validation` value must be true.

| Variable                      | Value                                |
| ----------------------------- | ------------------------------------ |
| `@@CTX:destination.qualified` | Fully qualified destination relation |
| `@@CTX:destination.schema`    | Destination schema                   |
| `@@CTX:destination.table`     | Destination relation name            |
| `@@CTX:model.name`            | Model name                           |
| `@@CTX:run.target`            | Active target name                   |
| `@@CTX:run.id`                | Current run ID                       |

## Python hooks

SQLBuild discovers `@hook` functions recursively under `hooks/`:

```python theme={null}
from sqlbuild.hooks import hook

@hook
def grant_analyst(ctx, role="analyst_role"):
    ctx.execute_sql(f"GRANT SELECT ON {ctx.destination.qualified} TO {role}")
```

Reference the hook by name and optionally pass keyword arguments:

```sql theme={null}
MODEL (
  materialized table,
  post_hooks [python("grant_analyst", role: "reader_role")],
);
```

SQL and Python hooks can appear in the same list.

## Hook context

Python hooks declare a `HookContext` parameter named `ctx`, `context`, `_ctx`, or `hook_context`. It need not be the first parameter when providers or configured arguments are also present:

| Field                                 | Description                                                     |
| ------------------------------------- | --------------------------------------------------------------- |
| `ctx.model_name`                      | Model being built                                               |
| `ctx.phase`                           | `pre_hooks` or `post_hooks`                                     |
| `ctx.hook_name`                       | Invoked hook name                                               |
| `ctx.run_id`                          | Current run ID                                                  |
| `ctx.target`                          | Active target                                                   |
| `ctx.vars`                            | Effective project variables                                     |
| `ctx.destination`                     | Destination relation metadata                                   |
| `ctx.adapter`                         | Adapter instance                                                |
| `ctx.connection`                      | Live connection                                                 |
| `ctx.execute_sql(sql)`                | Execute SQL                                                     |
| `ctx.query(sql)`                      | Execute SQL and return rows                                     |
| `ctx.log(message)`                    | Write run output                                                |
| `ctx.skip(reason="...", mode="soft")` | Return a soft or hard skip result; arguments are keyword-only   |
| `ctx.providers`                       | Access discovered [providers](/concepts/python-nodes/providers) |

Providers may also be injected into hook parameters by name.

## Skip timing

Returning `ctx.skip(...)` from a pre-hook prevents materialization. Returning it from a post-hook changes the reported execution result, but the relation has already been created, promoted, or incrementally updated and audited.

`mode="hard"` blocks downstream nodes. A soft skip does not automatically block every downstream node; scheduler propagation depends on the other upstream results.

## Failure timing

Post-hooks are not promotion gates. A failed post-hook marks the model run as failed after warehouse mutation has already occurred. Put logic that must prevent materialization in a pre-hook, contract, pre-promotion audit, or the materialization itself.

## Discovery and validation

* Files beginning with `_`, including `__init__.py`, are skipped.
* Hook names must be unique across the project.
* The decorator accepts optional `name` and `description` arguments.
* Unknown hooks, unknown keyword arguments, and missing required arguments fail compilation.
* A function without `**kwargs` rejects undeclared hook arguments.
