[1.12.3 Bug] `statically_parse_unrendered_config` stringifies list/dict config values, causing false positives in `upgrade_manifest_json_dbt_version` / `state:modified`
- Dominant language
- Rust
- Stars
- 13.8k
- Forks
- 2.6k
- Avg merge
- 21h 31m
- Merged PRs (30d)
- 56
Description
### Is this a new bug in dbt-core?
- [x] I believe this is a new bug in dbt-core
- [x] I have searched the existing issues, and I could not find an existing issue for this bug
### 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
1. `{{ config(cluster_by=['id']) }}` in a model's SQL.
2. `dbt parse`, keep the manifest (the "new" side).
3. Copy it, edit `metadata.dbt_version` to a different version string (the "old" side).
4. `dbt ls --select state:modified --state `
**Expected:** model not selected. **Actual:** selected — `unrendered_config['cluster_by']`
was rewritten from `['id']` to `"['id']"` by the upgrade step.
### Relevant log output
```shell
$ dbt ls --select state:modified --state ./old-manifest/
model.my_project.my_model
(despite identical `raw_code`/checksum on both sides)
```
### Environment
```markdown
- 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).
Contributor guide
Assessment
This issue has not been assessed yet.