Skip to main content

dbt-hodor: Hold Old Data, Or Rebuild

dbt-hodor

CI Python 3.10 to 3.12 dbt-core 1.12 Apache-2.0 Documentation

Documentation | Interactive guide with a run simulator | Design spec

dbt-hodor is a free, self-hosted alternative to dbt Labs' dbt State. It runs inside dbt. For every selected node it decides whether to reuse the object that is already in the warehouse or to build it again. A node is reused when its logic has not changed and its upstream data has not changed since the last build.

It needs no dbt platform account and sends nothing to external services. State lives in a Delta table on Databricks, in a Postgres database such as Databricks Lakebase, or in a local DuckDB file.

Status: early development. hodor has end-to-end tests on dbt-duckdb and live tests on a Databricks SQL warehouse and on Databricks Lakebase; the test docs list what they cover. It has not run in a large production project yet. Before enabling reuse in production, run in shadow mode for at least a week of scheduled runs and review its decisions. See the rollout guidance. Changes between releases, including the breaking ones, are in Upgrading.

Why "hodor"

The name is a backronym: Hold Old Data, Or Rebuild. That is the only decision hodor makes, once per node.

hodor holds the door in front of your warehouse. A node gets through to a rebuild only when its code changed or new data arrived upstream. Everything else keeps the data it already has.

How it works

For each node in dbt run, dbt build, dbt test, dbt seed or dbt snapshot, hodor:

  1. Hashes the logic. For a seed this is the CSV file. Otherwise the compiled SQL is normalized with sqlglot (Databricks dialect), so case, quoting, comments and table aliases do not count as changes. Values that differ on every run, such as invocation_id, are replaced first. The node config is hashed as well.
  2. Checks the upstream data. Each upstream table has a data version. On Databricks it comes from the Delta commit history; maintenance commits such as OPTIMIZE or VACUUM do not count as data changes. On DuckDB it is a content fingerprint.
  3. Decides. It compares both against the record of the node's last build and returns reuse or build, with a reason code.
  4. Records. It appends one record per node to the state table.

A run with no code changes skips sqlglot entirely: when the compiled SQL is byte-identical to the last build, hodor reuses the stored analysis. For 10,000 compiled models, hashing then takes about 0.1 seconds instead of about 4 minutes.

Safety: fail open

Every failure leads to a build, never to a reuse. An error while deciding, a store that cannot be read, a table whose data version is unknown, an input hodor cannot trace (for example read_files(...)): in each case the node builds as plain dbt would. hodor never fails a dbt run.

Reason codes

Reason Decision
full_refresh build
unsupported build
no_history build
relation_missing build
code_changed build
config_changed build
target_modified build
select_star_view build
view_unchanged reuse
unknown_upstream build
upstream_code_changed build
no_new_data reuse
within_lag_tolerance reuse
new_upstream_data build
volatile_sql build
hodor_error build

The rules are checked in this order; the first match decides. dbt-hodor explain -s <model> shows the reason for one node, with the hashes and upstream data versions it compared. The full rules are in the design spec.

Modes

Mode Behavior
off hodor does nothing.
shadow (default) hodor decides and records, but every node builds. Use it to check the decisions before you trust them.
on hodor reuses nodes.

Installing the package never skips anything until someone sets on.

Configuration

Node configs

hodor reads the same state: block as dbt State, so a project can switch between the two. Set it in YAML:

# dbt_project.yml
models:
  my_project:
    +state:
      lag_tolerance: 4h
      require_fresh_data_from: any
Key Default Values
lag_tolerance 45m 0s, 45m, 4h, 1d, 1w, integer seconds, or {count: 4, period: hour}
require_fresh_data_from any any: build when any upstream has new data. all: build only when all of them have.
compare_unrendered_code false true: build only when both the rendered and the unrendered code changed
execute_hooks_on_any_reuse false true: run pre- and post-hooks on a reused node. execute_hooks_on_reuse is also accepted.
evaluate_volatile_sql auto Auto: volatile functions such as current_date(), current_user() and current_timestamp() count as an upstream that changes, unless they only fill a stamp column. true: stamp columns count too. false: they never count (the dbt State default).

pre_clone, allow_clones and defer_to_target are accepted and ignored, with a warning.

Settings

Set them on the profiles.yml target, or for one run with --vars '{hodor_mode: off}'. Precedence: environment variable, then --vars, then the profile key.

Profile key Env var Default Values
hodor_mode HODOR_MODE shadow off, shadow, on
hodor_store HODOR_STORE delta delta, duckdb, postgres (alias lakebase)
hodor_database HODOR_DATABASE the target's catalog (or database) Databricks catalog; Postgres database (later). Not used by duckdb
hodor_schema HODOR_SCHEMA none for delta (required); main for duckdb schema name
hodor_table HODOR_TABLE hodor_state table name
hodor_path HODOR_PATH .hodor/state.duckdb DuckDB database file, for duckdb
hodor_metadata_threads HODOR_METADATA_THREADS 8 1-64
hodor_lookback_days HODOR_LOOKBACK_DAYS 90 1-3650

Name parts may contain letters, digits, _ and -. hodor always quotes them.

Where the state lives

Store Location Created on first use
delta <hodor_database>.<hodor_schema>.<hodor_table> the table only. The schema must exist, because creating one needs catalog-level grants
duckdb table <hodor_schema>.<hodor_table> in the file hodor_path the file, the schema and the table
postgres (alias lakebase) <hodor_schema>.<hodor_table> in the Postgres database hodor_database the schema and the table

delta has no default schema on purpose: hodor writes nothing to a catalog until someone names the schema. Without it, hodor turns itself off with a warning.

# profiles.yml
my_project:
  target: prod
  outputs:
    prod:
      type: databricks
      catalog: my_catalog
      # ... connection settings ...
      hodor_mode: shadow
      hodor_schema: ops
      # state table: my_catalog.ops.hodor_state
    local:
      type: duckdb
      path: dev.duckdb
      hodor_store: duckdb
      # state table: main.hodor_state in .hodor/state.duckdb

The state table decides what gets skipped in prod. Grant MODIFY on it only to the job service principals.

Scope of v1

Supported: models materialized as table, incremental (append, merge, delete+insert, insert_overwrite, replace_where; an unset strategy counts as merge) and view, seeds, snapshots, and data tests (generic and singular), on dbt-databricks 1.12.7 and dbt-duckdb, with dbt-core 1.12.x.

Model versions: each version is its own node with its own table and records. Moving latest_version rebuilds the models that ref() it.

Slim CI: --defer and --favor-state are supported, and each push of a pull request reuses what the last push built. See Slim CI. The node configs pre_clone, allow_clones and defer_to_target are not.

A pull request builds its changed model in the CI catalog, reads unchanged parents from the base target through defer, and keeps its build records apart from the base target's

Always built as plain dbt: Python models, microbatch, materialized views, streaming tables, metric views, custom materializations, custom incremental strategies and unit tests.

Known limit: a change to dbt's or the adapter's own macros, for example a dbt-databricks upgrade, does not show in any hash. Run once with HODOR_MODE=shadow after such an upgrade: shadow builds every node and records the builds. off records nothing, so the old records would still count. Project and package macros that a materialization or a hook runs are in the config hash.

Development

Requires uv. Local development and tests run on DuckDB, with no Databricks access.

uv sync
uv run pytest
uv run ruff check
uv run ruff format

The live Databricks test is opt-in: see docs/development.md.

dbt-hodor is available on PyPI:

pip install dbt-hodor

To build a wheel from the source checkout, run uv build.

Contributing

Issues and pull requests are welcome. CONTRIBUTING.md has the setup, the checks to run, and what a decision change needs. A wrong reuse, where hodor skipped a node it should have rebuilt, is a security-grade bug: report it privately through the Security tab.

License

Apache-2.0. NOTICE carries the attribution for the derived files and the trademark notice; LICENSE is the full licence text.

Metadata

Release files for dbt-hodor 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for dbt-hodor 0.2.0
File Size Uploaded Provenance
dbt_hodor-0.2.0.tar.gz 51.7 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for dbt-hodor 0.2.0
File Interpreter ABI Platform
dbt_hodor-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 108.8 kB

Release files / dbt_hodor-0.2.0.tar.gz

Download URL dbt_hodor-0.2.0.tar.gz
Size 51.7 kB
Tags Source
SHA-256 checksum
How to use checksums
de7adcb167b3ab73c4e9bebc3717d145bc98aee14f78984c3113198f764ff6ca
BLAKE2b-256 checksum
How to use checksums
7bfa4015410b43c8a5a3f1a6d3ffc641ce7280707ae33a98374354be050b01b2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.

Transparency log

Release files / dbt_hodor-0.2.0-py3-none-any.whl

Download URL dbt_hodor-0.2.0-py3-none-any.whl
Size 57.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2668ce8b56fbf91b90a52ae248f8d1a13c3ce0b70028d32652f26fba90d5800a
BLAKE2b-256 checksum
How to use checksums
320d1f2cbebb079feec6cbfd0c6c0b3f785822ed4be72fdaeab00c3636a95a8c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.

Transparency log

Security for dbt-hodor 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for dbt_hodor-0.2.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page