Running the Tuva Project locally on DuckDB is attractive — no warehouse costs, no cloud credentials during development, sub-second iteration. But the combination of Tuva 0.17.2, dbt 1.11, and DuckDB 1.10 has real rough edges that aren’t documented anywhere.
Here is what actually broke on a 167k claims dataset, and how each failure was resolved.
The setup
Stack:
- dbt 1.11 with dbt-duckdb adapter
- DuckDB 1.10
- Tuva Project 0.17.2
- 167k synthetic Medicare medical claims and 2.3k eligibility rows, loaded from S3
Running dbt run on the full Tuva package processes 879 models on this dataset.
Error 1: undefined macro 'limit_zero'
A batch of models fail during compilation with:
Compilation Error in model quality_measures__int_adh_statins_numerator
'limit_zero' is undefined. Did you mean one of these: dbt_utils.limit_zero
Tuva uses limit_zero internally to generate empty table structures during schema materialization. The macro exists inside dbt-utils but dbt’s macro resolution doesn’t automatically cross package boundaries without an explicit namespace prefix.
When dbt compiles a Tuva model that calls {{ limit_zero() }}, it looks in:
- Your project’s
macros/directory - The Tuva package’s own macros
- The global dbt namespace
It does not walk into dbt-utils unless the Tuva macro was written as {{ dbt_utils.limit_zero() }}. In some versions it’s written as just {{ limit_zero() }}, which breaks.
Fix: create macros/limit_zero.sql in your local project:
{% macro default__limit_zero() %}
limit 0
{% endmacro %}
{% macro duckdb__limit_zero() %}
limit 0
{% endmacro %}
dbt resolves macros from your project namespace first. Adding both default__ and duckdb__ covers adapter dispatch for DuckDB and any fallback adapter.
Error 2: DuckDB file lock conflict with DBeaver
Running dbt run while DBeaver has the same .duckdb file open produces:
IO Error: Could not set lock on file "tuva.duckdb": Resource temporarily unavailable
DuckDB 1.10 enforces a single-writer model. Only one process can hold a write connection to a given database file at a time. DBeaver opens connections in read-write mode by default and holds that connection open as long as the client is running.
Fix: in DBeaver’s connection settings for the DuckDB connection, add the property:
read_only = true
DBeaver will still execute SELECT queries and browse schema, but it won’t acquire the write lock. dbt run then proceeds normally.
Error 3: DuckDB internal assertion failures
Two models fail with internal errors rather than SQL errors:
hcc_recapture__int_gap_status
quality_measures__int_adh_statins_numerator
The error message looks like:
Internal Error: Assertion failed (binder.cpp): column reference 'group_last' could not be resolved
Root cause: both models use complex CTEs where one CTE defines a window function that produces a column alias (group_last, best_past_rank), and a subsequent CTE references that alias inside another window’s ORDER BY or PARTITION BY clause. DuckDB 1.10 fails to bind these cross-CTE column references correctly.
This is a DuckDB 1.10 regression. The same models run cleanly on DuckDB 1.8.
Workarounds:
- Downgrade to DuckDB 1.8 or 1.9
- Exclude the affected models:
dbt run --exclude hcc_recapture__int_gap_status quality_measures__int_adh_statins_numerator - Wait for Tuva 0.18.x which is expected to flatten those CTEs
Final run result
After applying the limit_zero fix and excluding the two assertion failures:
875 PASS, 4 ERROR, 0 WARN
The four errors are all DuckDB 1.10 assertion failures — not logic errors, not data issues. All HEDIS quality measures (except the statin adherence numerator), all HCC suspecting marts, CCSR, and encounter marts run cleanly.
Summary
| Issue | Root cause | Fix |
|---|---|---|
| limit_zero undefined | Macro resolution doesn’t cross package namespaces | Add local limit_zero.sql macro |
| DuckDB lock conflict | DBeaver holds write lock by default | Set DBeaver connection to read_only = true |
| Assertion failures | DuckDB 1.10 window CTE column binding regression | Exclude models or downgrade DuckDB |