Is this a new bug in dbt-core?
Current Behavior
When loading a --state manifest whose metadata.dbt_version differs from the
running dbt's version, upgrade_manifest_json_dbt_version
(core/dbt/artifacts/schemas/upgrades/upgrade_manifest_dbt_version.py) re-derives each
node's unrendered_config by re-parsing its raw SQL's {{ config(...) }} call via
statically_parse_unrendered_config (core/dbt/clients/jinja_static.py) — gated by
state_modified_compare_more_unrendered_values (default true since 1.12).
That parser's construct_static_kwarg_value is typed -> str and stringifies
list/dict values unconditionally. {{ config(cluster_by=['id']) }} ends up with
unrendered_config['cluster_by'] == "['id']" instead of the real list ['id'].
Since same_config/same_contents compare unrendered_config with plain ==, this
type mismatch makes state:modified falsely flag the model as changed — even though
same_body/checksum (the actual SQL) is identical.
Affects any model with a list/dict-valued config key declared inline (cluster_by,
partition_by, grants, labels, meta, unique_key as a list, etc.) whenever
state:modified compares manifests from two different dbt-core versions/builds.
Expected Behavior
The static parser should preserve the real type of list/dict config values (or at
least produce something that round-trips via ast.literal_eval), so the upgrade merge
doesn't introduce a type mismatch against a freshly-parsed, unchanged model.
Steps To Reproduce
{{ config(cluster_by=['id']) }} in a model's SQL.
dbt parse, keep the manifest (the "new" side).
- Copy it, edit
metadata.dbt_version to a different version string (the "old" side).
dbt ls --select state:modified --state <old-manifest-dir>
Expected: model not selected. Actual: selected — unrendered_config['cluster_by']
was rewritten from ['id'] to "['id']" by the upgrade step.
Relevant log output
$ dbt ls --select state:modified --state ./old-manifest/
model.my_project.my_model
(despite identical `raw_code`/checksum on both sides)
Environment
- OS: macOS
- Python: 3.13.0
- dbt: 1.12.3 (also present in 1.12.0)
Which database adapter are you using with dbt?
bigquery
Additional Context
Root-caused against a real ~450-model project: calling same_body, same_config,
same_persisted_description, same_fqn, same_database_representation,
same_contract, same_ref_representation directly (via dbt.contracts.state.PreviousState)
reproduced the flagged/not-flagged status of every model exactly (366/366).
Is this a new bug in dbt-core?
Current Behavior
When loading a
--statemanifest whosemetadata.dbt_versiondiffers from therunning dbt's version,
upgrade_manifest_json_dbt_version(
core/dbt/artifacts/schemas/upgrades/upgrade_manifest_dbt_version.py) re-derives eachnode's
unrendered_configby re-parsing its raw SQL's{{ config(...) }}call viastatically_parse_unrendered_config(core/dbt/clients/jinja_static.py) — gated bystate_modified_compare_more_unrendered_values(defaulttruesince 1.12).That parser's
construct_static_kwarg_valueis typed-> strand stringifieslist/dict values unconditionally.
{{ config(cluster_by=['id']) }}ends up withunrendered_config['cluster_by'] == "['id']"instead of the real list['id'].Since
same_config/same_contentscompareunrendered_configwith plain==, thistype mismatch makes
state:modifiedfalsely flag the model as changed — even thoughsame_body/checksum (the actual SQL) is identical.Affects any model with a list/dict-valued config key declared inline (
cluster_by,partition_by,grants,labels,meta,unique_keyas a list, etc.) wheneverstate:modifiedcompares manifests from two different dbt-core versions/builds.Expected Behavior
The static parser should preserve the real type of list/dict config values (or at
least produce something that round-trips via
ast.literal_eval), so the upgrade mergedoesn't introduce a type mismatch against a freshly-parsed, unchanged model.
Steps To Reproduce
{{ config(cluster_by=['id']) }}in a model's SQL.dbt parse, keep the manifest (the "new" side).metadata.dbt_versionto a different version string (the "old" side).dbt ls --select state:modified --state <old-manifest-dir>Expected: model not selected. Actual: selected —
unrendered_config['cluster_by']was rewritten from
['id']to"['id']"by the upgrade step.Relevant log output
$ dbt ls --select state:modified --state ./old-manifest/ model.my_project.my_model (despite identical `raw_code`/checksum on both sides)Environment
Which database adapter are you using with dbt?
bigquery
Additional Context
Root-caused against a real ~450-model project: calling
same_body,same_config,same_persisted_description,same_fqn,same_database_representation,same_contract,same_ref_representationdirectly (viadbt.contracts.state.PreviousState)reproduced the flagged/not-flagged status of every model exactly (366/366).