ActionBoxBlog

Data engineerData science

dbt full-refresh approval: gate expensive models and backfills

A copy-paste dbt full-refresh approval workflow that gates expensive models and backfills with warehouse-cost context, timeouts, and a safe reject path.

A dbt run --full-refresh on a large fact table can burn a month's warehouse budget in minutes. The irony: the expensive command is usually the one nobody watches. This workflow puts a human decision in front of every full refresh, backfill, and heavy model run — only when it's expensive, never in the happy path.

dbt full-refresh approval workflow

A Python guard that runs before dbt run:

  1. Inspects the command line for --full-refresh, --select, and the model's cost tier
  2. Skips the gate for cheap incremental runs (zero friction)
  3. Creates an Actionbox approval with warehouse-cost context for anything risky
  4. Blocks until a data engineer approves or rejects; reject fails the job without touching the warehouse

Step 1: the guard script

python
import os
import subprocess
import sys
from datetime import datetime, timedelta, timezone
from actionbox import Actionbox

box = Actionbox(api_key=os.environ["ACTIONBOX_TOKEN"])
RISKY_FLAGS = ("--full-refresh", "--backfill", "--select", "--defer")

def run(args):
    if not any(flag in args for flag in RISKY_FLAGS):
        return  # cheap path: no gate
    decision = box.ask(
        title=f"Run `dbt {' '.join(args)}` against {os.environ['DBT_TARGET']}?",
        description=(
            "Full refresh or selected build detected. Warehouse cost tier: "
            f"{os.environ.get('COST_TIER', 'unknown')}. "
            "This may take >30 minutes and consume significant compute."
        ),
        interaction={"type": "boolean", "label": "Approve this dbt run?"},
    )
    if decision is not True:
        print("dbt run rejected or expired")
        sys.exit(1)
    subprocess.run(["dbt", *args], check=True)

if __name__ == "__main__":
    run(sys.argv[1:])

Step 2: wire it into your scheduler

Every orchestrator can call this — Dagster, Airflow, Prefect, or plain cron. The decision blocks the process until a human answers (or it times out).

bash
python scripts/guard_dbt.py run --full-refresh --select fct_orders

If your pipeline runs in a container, pass the token in:

yaml
steps:
  - name: Guard full refresh
    env:
      ACTIONBOX_TOKEN: ${{ secrets.ACTIONBOX_TOKEN }}
    run: python scripts/guard_dbt.py run --full-refresh --select fct_orders

Step 3: context that makes approvers confident

A bare "approve this?" is ignored. Attach what an on-call data engineer actually needs:

python
decision = box.ask(
    title="Full refresh of fct_orders?",
    context=[{
        "type": "key_value",
        "items": {
            "model": "fct_orders",
            "last_refresh": "2026-07-20",
            "row_count": "1.2B",
            "estimated_cost": "$214",
            "dry_run_passed": "true",
        }}],
    interaction={"type": "boolean", "label": "Proceed"},
)

The dashboard renders this before the buttons — approvers decide in seconds, not meetings.

Timeout policy

Warehouse jobs are usually scheduled at night, and nobody is awake to approve. Set a deliberate timeout policy:

  • Nightly batch: allow 60 minutes, then on_expire returns expired → page the on-call engineer
  • Interactive runs: 15 minutes, reject on timeout — nobody leaves a $2K job waiting on a coin flip
python
decision = box.ask(
    ...,
    expires_at=(datetime.now(timezone.utc) + timedelta(hours=1)).isoformat(),
    on_expire={"type": "return_expired"},
)

Why this pattern scales

The same guard works for any expensive data operation — not just dbt:

Because the decision is an API call, not a Slack thread, it works from any scheduler, records who decided and why, and keeps a full audit trail for warehouse-cost review.

Try it

  1. pip install actionbox in your dbt runner
  2. Set ACTIONBOX_TOKEN from a Source you create in the dashboard
  3. Run a --full-refresh and watch the gate pause

Create a free Source · Python SDK · GitHub Actions deployment gate

Try this workflow in minutes

Create a free Source, then run the exact commands from this post against the live API — no approval infrastructure to build.

S
Suson Sapkota

Founder, Actionbox