Skip to content

fix(parser): don't reassign a disabled package resource's schema.yml patch to an unrelated same-named node - #16198

Open
Kunal8954 wants to merge 1 commit into
dbt-labs:1.latestfrom
Kunal8954:fix/disabled-package-seed-schema-yml-patch
Open

Kunal8954 wants to merge 1 commit into
dbt-labs:1.latestfrom
Kunal8954:fix/disabled-package-seed-schema-yml-patch

Conversation

@Kunal8954

Copy link
Copy Markdown

NodePatchParser.parse_patch() resolves a models/seeds/snapshots schema.yml patch by first looking it up scoped to its own package, then falling back to an unscoped, cross-package lookup if that fails. When a package's own resource is disabled (e.g. via +enabled: false in a dependent project), the scoped lookup correctly returns nothing — but the unscoped fallback can then find and bind the patch to an unrelated same-named resource in a different package. If that other resource already has its own patch, this raises DuplicatePatchPathError even though only one schema.yml entry actually describes an enabled resource.

This is a common pattern: disable a shared package's seed and define a same-named seed in the root project to override it. Both describe the seed in their own schema.yml.

Fix: before falling back to the unscoped lookup, check whether the resource's own package has a disabled node with this name — if so, let the patch flow into the existing disabled-node handling instead.

Added a regression test using a local package dependency (tests/functional/dependencies/test_disabled_dependency_seed_schema_patch.py) — confirmed it reproduces the exact reported error before the fix and passes after, run against a real Postgres instance.

Fixes #15562

…patch to an unrelated same-named node

`NodePatchParser.parse_patch()` resolved a models/seeds/snapshots schema.yml
patch with:

    unique_id = ref_lookup.get_unique_id(patch.name, self.project.project_name, None) \
        or ref_lookup.get_unique_id(patch.name, None, None)

When a package's own resource with `patch.name` is disabled (e.g. via
`+enabled: false` in a dependent project's dbt_project.yml), the scoped
lookup correctly returns None. But the unscoped fallback then searches
*every* package for an enabled node with that name -- so if the root
project (or another package) happens to define its own resource under the
same name, the disabled package's schema.yml patch gets silently bound to
that unrelated node instead of falling through to the "handle disabled
nodes" branch. If that other node already has a patch_path (its own
schema.yml already patched it), this raises DuplicatePatchPathError:
"dbt found two schema.yml entries for the same resource named X" -- even
though only one schema.yml entry actually describes an enabled resource.

This is a common pattern for projects built on a shared package: disable
the package's seed and define a same-named seed in the root project to
override it. Both the package and the root project describe the seed in
their own schema.yml.

Fix: before falling back to the unscoped cross-package lookup, check
whether the resource's own package has a disabled node with this name. If
so, let this patch flow into the existing disabled-node handling instead
of resolving it against an unrelated package's node.

Added a regression test (tests/functional/dependencies/
test_disabled_dependency_seed_schema_patch.py) using a local package
dependency, confirmed it reproduces the exact reported error before the
fix and passes after.

Fixes dbt-labs#15562

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015kAvbZ6SeKgp6jbGcvpXSh
@Kunal8954
Kunal8954 requested a review from a team as a code owner September 4, 2026 07:43
Copilot AI lite review requested due to automatic review settings September 4, 2026 07:43
@cla-bot cla-bot Bot added the cla:yes label Sep 4, 2026
@Kunal8954
Kunal8954 changed the base branch from main to 1.latest September 4, 2026 07:46

@codescene-delta-analysis codescene-delta-analysis Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gates Failed
Prevent hotspot decline (10 hotspots with Complex Method, Complex Conditional, Bumpy Road Ahead, Overall Code Complexity, Lines of Code in a Single File, Number of Functions in a Single Module, Low Cohesion, Code Duplication, Deep, Nested Complexity, Excess Number of Function Arguments, Large Method)
New code is healthy (32 new files with code health below 9.00)
Enforce critical code health rules (30 files with Low Cohesion, Bumpy Road Ahead, Deep, Nested Complexity)
Enforce advisory code health rules (56 files with Complex Method, Large Method, Excess Number of Function Arguments, Complex Conditional, Overall Code Complexity, Lines of Code in a Single File, Number of Functions in a Single Module, Code Duplication)

Our agent can fix these. Install it.

Reason for failure
Prevent hotspot decline Violations Code Health Impact
manifest.py 10 rules in this hotspot 1.91 Suppress
providers.py 8 rules in this hotspot 3.61 Suppress
nodes.py 7 rules in this hotspot 3.66 Suppress
schemas.py 6 rules in this hotspot 4.12 Suppress
test_manifest.py 6 rules in this hotspot 4.93 Suppress
manifest.py 7 rules in this hotspot 5.23 Suppress
test_parser.py 7 rules in this hotspot 5.99 Suppress
expected_manifest.py 2 rules in this hotspot 6.30 Suppress
test_deprecations.py 4 rules in this hotspot 7.11 Suppress
project.py 4 rules in this hotspot 8.47 Suppress
New code is healthy Violations Code Health Impact
manifest.py 10 rules 1.91 Suppress
partial.py 7 rules 2.04 Suppress
providers.py 8 rules 3.61 Suppress
nodes.py 7 rules 3.66 Suppress
schemas.py 6 rules 4.12 Suppress
test_manifest.py 6 rules 4.93 Suppress
manifest.py 7 rules 5.23 Suppress
runnable.py 6 rules 5.72 Suppress
test_list.py 4 rules 5.86 Suppress
unit_tests.py 6 rules 5.96 Suppress
test_parser.py 7 rules 5.99 Suppress
jsonschemas.py 5 rules 6.07 Suppress
run.py 6 rules 6.11 Suppress
expected_manifest.py 2 rules 6.30 Suppress
read_files.py 6 rules 6.52 Suppress
flags.py 4 rules 6.62 Suppress
test_deprecations.py 4 rules 7.11 Suppress
jinja_static.py 5 rules 7.29 Suppress
base.py 3 rules 7.33 Suppress
test_project.py 2 rules 7.46 Suppress
fusion.py 4 rules 7.71 Suppress
project.py 5 rules 7.77 Suppress
list.py 4 rules 7.87 Suppress
test_run.py 3 rules 8.03 Suppress
tracking.py 1 rule 8.28 Suppress
main.py 1 rule 8.28 Suppress
project.py 4 rules 8.47 Suppress
requires.py 3 rules 8.52 Suppress
test_fusion.py 2 rules 8.55 Suppress
option_types.py 4 rules 8.68 Suppress
artifact_upload.py 2 rules 8.80 Suppress
runtime.py 4 rules 8.82 Suppress
Enforce critical code health rules Violations Code Health Impact
manifest.py 3 critical rules 1.91 Suppress
partial.py 2 critical rules 2.04 Suppress
providers.py 2 critical rules 3.61 Suppress
nodes.py 3 critical rules 3.66 Suppress
schemas.py 2 critical rules 4.12 Suppress
test_manifest.py 2 critical rules 4.93 Suppress
manifest.py 2 critical rules 5.23 Suppress
runnable.py 2 critical rules 5.72 Suppress
test_list.py 2 critical rules 5.86 Suppress
unit_tests.py 2 critical rules 5.96 Suppress
test_parser.py 1 critical rule 5.99 Suppress
jsonschemas.py 2 critical rules 6.07 Suppress
run.py 2 critical rules 6.11 Suppress
read_files.py 2 critical rules 6.52 Suppress
flags.py 1 critical rule 6.62 Suppress
test_deprecations.py 1 critical rule 7.11 Suppress
jinja_static.py 2 critical rules 7.29 Suppress
base.py 2 critical rules 7.33 Suppress
test_project.py 1 critical rule 7.46 Suppress
fusion.py 1 critical rule 7.71 Suppress
project.py 2 critical rules 7.77 Suppress
list.py 2 critical rules 7.87 Suppress
test_run.py 1 critical rule 8.03 Suppress
project.py 1 critical rule 8.47 Suppress
requires.py 1 critical rule 8.52 Suppress
test_fusion.py 1 critical rule 8.55 Suppress
option_types.py 1 critical rule 8.68 Suppress
runtime.py 1 critical rule 8.82 Suppress
macros.py 1 critical rule 9.10 Suppress
init.py 1 critical rule 9.28 Suppress
Enforce advisory code health rules Violations Code Health Impact
manifest.py 7 advisory rules 1.91 Suppress
partial.py 5 advisory rules 2.04 Suppress
providers.py 6 advisory rules 3.61 Suppress
nodes.py 4 advisory rules 3.66 Suppress
schemas.py 4 advisory rules 4.12 Suppress
test_manifest.py 4 advisory rules 4.93 Suppress
manifest.py 5 advisory rules 5.23 Suppress
runnable.py 4 advisory rules 5.72 Suppress
test_list.py 2 advisory rules 5.86 Suppress
unit_tests.py 4 advisory rules 5.96 Suppress
test_parser.py 6 advisory rules 5.99 Suppress
jsonschemas.py 3 advisory rules 6.07 Suppress
run.py 4 advisory rules 6.11 Suppress
expected_manifest.py 2 advisory rules 6.30 Suppress
read_files.py 4 advisory rules 6.52 Suppress
flags.py 3 advisory rules 6.62 Suppress
test_deprecations.py 3 advisory rules 7.11 Suppress
jinja_static.py 3 advisory rules 7.29 Suppress
base.py 1 advisory rule 7.33 Suppress
test_project.py 1 advisory rule 7.46 Suppress
fusion.py 3 advisory rules 7.71 Suppress
project.py 3 advisory rules 7.77 Suppress
list.py 2 advisory rules 7.87 Suppress
test_run.py 2 advisory rules 8.03 Suppress
tracking.py 1 advisory rule 8.28 Suppress
main.py 1 advisory rule 8.28 Suppress
project.py 3 advisory rules 8.47 Suppress
requires.py 2 advisory rules 8.52 Suppress
test_fusion.py 1 advisory rule 8.55 Suppress
option_types.py 3 advisory rules 8.68 Suppress
artifact_upload.py 2 advisory rules 8.80 Suppress
runtime.py 3 advisory rules 8.82 Suppress
test_configs_in_schema_files.py 2 advisory rules 9.02 Suppress
macros.py 2 advisory rules 9.10 Suppress
test_context.py 2 advisory rules 9.10 Suppress
test_freshness.py 2 advisory rules 9.10 Suppress
test_requires.py 2 advisory rules 9.24 Suppress
init.py 1 advisory rule 9.28 Suppress
test_partial.py 1 advisory rule 9.33 Suppress
osi.py 1 advisory rule 9.39 Suppress
test_list.py 1 advisory rule 9.39 Suppress
test_macro_annotations.py 1 advisory rule 9.39 Suppress
test_metric_configs.py 1 advisory rule 9.39 Suppress
build.py 1 advisory rule 9.39 Suppress
test_jsonschemas.py 1 advisory rule 9.39 Suppress
freshness.py 2 advisory rules 9.39 Suppress
test_jinja.py 1 advisory rule 9.39 Suppress
test_deprecated_version.py 1 advisory rule 9.39 Suppress
test_dbt_runner.py 1 advisory rule 9.55 Suppress
test_manifest.py 1 advisory rule 9.69 Suppress
test_flags.py 1 advisory rule 9.69 Suppress
test_tracking.py 1 advisory rule 9.69 Suppress
test_warn_error_options.py 1 advisory rule 9.69 Suppress
configured.py 1 advisory rule 9.69 Suppress
macros.py 1 advisory rule 9.69 Suppress
jinja.py 1 advisory rule 9.69 Suppress

See analysis details in CodeScene

Quality Gate Profile: Clean Code Collective
Install CodeScene MCP: safeguard and uplift AI-generated code. Catch issues early with our IDE extension and CLI tool.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The change set is large and cross-cutting (parser/partial parsing/CLI flags/telemetry/artifacts/workflows), so it needs careful human review to validate interactions and backward-compatibility.

Pull request overview

This PR addresses a parsing bug where a schema.yml patch belonging to a disabled resource in its own package could be incorrectly rebound (via an unscoped fallback lookup) to an unrelated same-named enabled resource in another package, causing spurious DuplicatePatchPathError (Fixes #15562). The change also includes several additional updates spanning parsing, partial parsing, telemetry, OpenTelemetry instrumentation, OSI discovery, and test coverage.

Changes:

  • Fix NodePatchParser.parse_patch() to avoid unscoped cross-package patch lookup when a same-named disabled node exists in the patch’s own package; add regression coverage.
  • Introduce/expand user-facing “hints” + related telemetry hooks; add OpenTelemetry context identifiers in Jinja context and additional OTEL span instrumentation.
  • Improve partial parsing cleanup and manifest/data structure behavior (including unit-test post-parse model resolution, OSI path handling, and lazy flat_graph/WritableManifest views).
File summaries
File Description
tests/unit/utils/project.py Add osi_paths to the test Project fixture.
tests/unit/test_tracking.py Add unit tests for hint-view + manage-state tracking events.
tests/unit/test_jsonschemas.py Add project-schema validation coverage for Databricks config keys.
tests/unit/test_hints.py New unit tests for hint cooldown/persistence and show/telemetry behavior.
tests/unit/task/test_list.py Add tests ensuring direct_parents is emitted correctly in dbt ls --output=json.
tests/unit/task/test_freshness.py Assert metadata freshness fallback returns RunStatus.Success.
tests/unit/task/test_build.py Add tests for reuse-relations hint gating and unit-test node-count bookkeeping.
tests/unit/parser/test_unit_tests.py Update unit-test parsing tests for deferred (post-parse) model resolution + new regressions.
tests/unit/parser/test_partial.py Add regressions for partial-parse cleanup of disabled tests.
tests/unit/parser/test_parser.py Update macro parsing expectations to include macro arguments.
tests/unit/parser/test_manifest.py Add tests for direct_parents backfill and long-parse hint logic.
tests/unit/contracts/graph/test_manifest.py Add msgpack round-trip test for manifest with lazy flat_graph views.
tests/unit/context/test_context.py Add regression for package/macro name collisions in macro namespace resolution.
tests/unit/context/test_base.py Add tests for new otel_trace_id() / otel_span_id() context members.
tests/unit/config/test_project.py Update Project.__str__ expectations for osi-paths.
tests/unit/config/test_fusion_warn_error_options.py New unit tests for tolerating Fusion-only warn/error option names.
tests/unit/clients/test_jinja.py Add tests for hook-count detection and hook-span naming helpers.
tests/unit/clients/test_jinja_static.py Expand unrendered-config static parsing test matrix + “no config() call” cases.
tests/unit/cli/test_flags.py Add tests for manage-state source attribution + snowflake-projects-otel flag.
tests/unit/artifacts/test_run_execution_result.py Add tests for state_decision_id serialization behavior.
tests/functional/unit_testing/test_ut_versioned_parse_order.py New functional regression for unit-test YAML parse order independence.
tests/functional/semantic_models/test_osi_parsing.py Switch OSI directory handling to lowercase osi/ and adjust assertions.
tests/functional/partial_parsing/test_pp_unit_tests.py New functional tests for partial-parse behavior around unit tests + model changes/disable/delete.
tests/functional/partial_parsing/test_pp_semantic_models.py New functional regression for inline metric cleanup across partial parses.
tests/functional/partial_parsing/test_pp_data_tests.py New functional regression for disabled/enabled toggling without ghost accumulation.
tests/functional/metrics/test_metrics.py Add time spine YAML fixture; adjust expected metric set.
tests/functional/metrics/test_metric_configs.py Add time spine YAML fixture across metric-config scenarios.
tests/functional/metrics/fixtures.py Add time spine YAML; adjust metric fixtures to nested cumulative params.
tests/functional/macros/test_macros.py Add regression for project var values containing Jinja.
tests/functional/macros/test_macro_annotations.py Disable macro-arg validation for a non-enforcement test via project flags.
tests/functional/list/test_list.py Update functional dbt ls JSON expectations to include direct_parents.
tests/functional/hints/test_hints.py New functional tests for hints, cooldown behavior, and flag-based suppression.
tests/functional/functions/test_overloaded_udfs.py Add regression ensuring overload-body dependencies are carried onto root function node.
tests/functional/deprecations/test_deprecations.py Update patch points for renamed adapter-config “extra keys” helpers.
tests/functional/dependencies/test_disabled_dependency_seed_schema_patch.py New functional regression for #15562 disabled dependency seed patch behavior.
tests/functional/defer_state/test_modified_state_schema_evolution.py Add scenario enabling “compare more unrendered values” flag.
tests/functional/configs/test_warn_error_options.py Add functional coverage for ignoring Fusion-only warn/error option names.
tests/functional/configs/test_configs_in_schema_files.py Explicitly set state_modified_compare_more_unrendered_values: false in test project config.
tests/functional/cli/test_v2_announcement.py New functional tests for v2 announcement firing on --version.
tests/functional/cli/test_requires.py Add allow-listed env vars for hints + snowflake OTEL.
tests/functional/cli/test_deprecated_version.py New functional tests for deprecated-version messaging/levels across entry points.
tests/functional/artifacts/expected_manifest.py Update expected unrendered_config contents for snapshots.
tests/conftest.py Add session-scoped OTEL tracer provider + per-test span isolation fixture.
docker/Dockerfile Update pinned OS package versions.
core/pyproject.toml Bump dependency bounds; add opentelemetry-api.
core/hatch.toml Add opentelemetry-sdk to dev/test dependencies.
core/dbt/v2_announcement.py New module emitting v2 availability message.
core/dbt/utils/artifact_upload.py Add Azure upload headers + safer error-code formatting to avoid leaking signed-request data.
core/dbt/tracking.py Add hint-view/manage-state/deprecated-version tracking helpers + shared context-event helper.
core/dbt/task/list.py Re-inject direct_parents into dbt ls --output=json output.
core/dbt/task/init.py Add deprecated-version warning on dbt init.
core/dbt/task/freshness.py Treat metadata freshness batch failure as non-fatal fallback (RunStatus.Success).
core/dbt/task/build.py Add reuse-relations hint logic; fix unit-test node-count accounting; propagate OTEL context into unit-test runner calls.
core/dbt/parser/unit_tests.py Make unit-test model resolution deterministic by deferring it to a post-parse pass; improve disabled routing.
core/dbt/parser/schemas.py Fix disabled-node patch reassignment bug (core fix for #15562); merge overload dependencies onto root functions; default macro arg validation to on.
core/dbt/parser/read_files.py Read OSI files from configurable osi_paths instead of a single constant directory.
core/dbt/parser/partial.py Clean up disabled tests and inline metrics correctly during partial parsing to prevent ghost duplicates.
core/dbt/parser/osi.py Support multiple OSI directories with directory deduplication; clear OSI-attributed nodes using multiple prefixes.
core/dbt/parser/macros.py Default macro-arg validation to on when flag is absent.
core/dbt/jsonschemas/jsonschemas.py Rename “config aliases” → “extra keys”; add Databricks adapter config keys as allowed extras.
core/dbt/hints.py New hints implementation with cooldown persistence + hint telemetry.
core/dbt/flags.py Include hints_enabled in flag dict export.
core/dbt/deprecated_version.py New deprecated-version notice + telemetry.
core/dbt/contracts/project.py Add osi_paths; add project flags for hints + snowflake OTEL; flip several legacy flag defaults.
core/dbt/contracts/graph/nodes.py Add direct_parents to ModelNode but strip it from manifest serialization.
core/dbt/contracts/graph/node_args.py Add direct_parents to ModelNodeArgs.
core/dbt/contracts/graph/manifest.py Make flat_graph + WritableManifest mappings lazy/no-copy and rebuild flat_graph after deserialization.
core/dbt/context/providers.py Pass BaseContext into Var helpers (typed) rather than a raw dict.
core/dbt/context/manifest.py Use a ChainMap-based optimization to avoid eagerly copying large macro namespaces into the context dict.
core/dbt/context/configured.py Pass BaseContext into ConfiguredVar helpers (typed) rather than a raw dict.
core/dbt/context/base.py Add otel_trace_id() and otel_span_id() context members; make Var rendering read from the current context mapping.
core/dbt/constants.py Change OSI directory constant to lowercase osi.
core/dbt/config/utils.py Add Fusion-only warn/error option partitioning + tolerant WarnErrorOptionsV2 construction.
core/dbt/config/runtime.py Propagate osi_paths into RuntimeConfig.
core/dbt/config/project.py Default osi_paths to both osi and OSI; serialize osi-paths in project config output.
core/dbt/clients/jinja.py Add OTEL hook-span instrumentation for run_hooks and safer hook-phase identification helpers.
core/dbt/clients/jinja_static.py Improve reconstruction of config() kwarg values to be more source-like and adapter-independent.
core/dbt/cli/requires.py Add manage-state telemetry emission and deprecated-version checks; plumb snowflake OTEL enablement into invocation context.
core/dbt/cli/params.py Add --hints-enabled and --snowflake-projects-otel; extend --version behavior to announce v2 + deprecated-version warning.
core/dbt/cli/option_types.py Route warn-error-options parsing through Fusion-tolerant builder.
core/dbt/cli/main.py Register new global flags.
core/dbt/cli/flags.py Build warn-error-options via Fusion-tolerant builder; add MANAGE_STATE_SOURCE attribution.
core/dbt/cli/exceptions.py Add mypy ignore for Click exit-code typing.
core/dbt/artifacts/schemas/upgrades/upgrade_manifest_dbt_version.py Upgrade stored manifest unrendered_config entries when “compare more unrendered values” is enabled.
core/dbt/artifacts/schemas/run/v5/run.py Add state_decision_id to RunResult and its message dict representation.
core/dbt/artifacts/resources/v1/catalog.py Add catalog_database field to catalog write integration config.
core/dbt/version.py Bump version to 1.14.0a1.
CHANGELOG.md Add links to 1.12 and 1.13 changelogs.
.github/workflows/release.yml Fix hatch version comparison logic (integer compare vs decimal).
.github/workflows/docs-issue.yml Update docs label from user docs to action:needs-docs.
.github/workflows/community-pr-notify.yml New Slack notification workflow for PRs labeled community.
.github/workflows/backport.yml Trigger backport workflow on merged+closed labeled PRs (and merged already-labeled PRs).
.github/pull_request_template.md Update label guidance to action:needs-docs.
.github/ISSUE_TEMPLATE/implementation-ticket.yml Update default label to action:needs-docs.
.changes/unreleased/* Add/update changie entries reflecting the above changes (including #15562 fix).
.changes/0.0.0.md Add links to 1.11–1.13 changelogs.
Review details
  • Files reviewed: 3/3 changed files
  • Comments generated: 0
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@Kunal8954

Copy link
Copy Markdown
Author

Flagging why the bot checks look bad here — none of it reflects this diff.

core/dbt/parser/schemas.py doesn't exist on main; main is the Rust v2.0
codebase now. This is a Python 1.x parser fix, so 1.latest is the only branch
it applies to, which is why I moved the base there.

The CodeScene job is "CodeScene Code Health Review (main)" — it analyses
against main, so it's diffing a Python branch against the Rust tree. That's
where "32 new files with code health below 9.00" comes from; this PR adds one
file. Copilot's "large and cross-cutting" summary is the same artifact — it
saw the main-based diff. Against 1.latest the actual change is 3 files,
+143/−1.

Unrelated, but both CONTRIBUTING.md files still say community PRs should
target main. For anything on the Python 1.x line that's no longer possible —
might be worth updating.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[1.11.12] Bug] Cannot overwrite seeds once deactivated in dbt-project.yml

2 participants