Skip to content

fix: Resolve outputs from modules deployed to a different scope - #3910

Open
Jerome Brown (oWretch) wants to merge 1 commit into
Azure:mainfrom
oWretch:owretch-fix-guid-hash-finalized
Open

fix: Resolve outputs from modules deployed to a different scope#3910
Jerome Brown (oWretch) wants to merge 1 commit into
Azure:mainfrom
oWretch:owretch-fix-guid-hash-finalized

Conversation

@oWretch

Copy link
Copy Markdown

Problem

Template expansion fails with Hash must be finalized before the hash value is retrieved when an object output from a module deployed to a different scope is used in a string function such as guid().

Fixes #3909

Root cause

Two defects, one masking the other.

Cross-scope resource IDs (primary). DeploymentVisitor.GetDeploymentScope resolved the subscriptionId, resourceGroup, and managementGroup properties of a deployment resource but ignored the scope property. For a module with scope: someManagementGroup, the nested deployment was registered under its real scope while the symbol resolved against the parent context scope. The two IDs disagreed, so TemplateContext.TryGetResource() returned false, reference() fell back to a synthetic mock, and .outputs.x.value.id collapsed to a null value.

Hash finalization (secondary). ExpressionHelpers.GetUnique only called TransformFinalBlock when the physically last argument was string-convertible. The unresolved value above landed in the last position, so the hash was never finalized and algorithm.Hash threw.

Changes

  • GetDeploymentScope now parses an explicit scope property via ResourceHelper.ResourceIdComponents, covering management group, subscription, resource group, and tenant scopes. Explicit scope takes precedence over the inherited parent context.
  • GetUnique always uses TransformBlock in the loop and calls TransformFinalBlock([], 0, 0) once afterwards. SHA-256 output depends only on the concatenated byte stream, not how it is split across blocks, so this is byte-identical for every input that previously succeeded.

Both fixes ship together deliberately. Fixing hash finalization alone would turn the exception into silently duplicate guid() values, because the unresolved argument is skipped rather than reported.

Tests

  • ExpressionHelpersTests.GetUnique_WhenLastArgumentIsNotAString_ShouldNotThrow — null last argument, object last argument, empty args.
  • BicepSymbolicNameTests.ProcessTemplate_WhenObjectOutputPropertyUsedInGuid_ShouldResolveUniqueNames with fixture Tests.Bicep.6* — a parent template creating a child management group, deploying a custom role definition module and a role assignment module to it with scope:. Asserts distinct resource names and that each roleDefinitionId resolves under the child management group.

Full PSRule.Rules.Azure.Tests suite passes (401/401). Also verified end to end against the real-world template that surfaced the issue: 10 role assignments, 10 distinct names, module outputs resolving to real role definition IDs.

Deployment resources with an explicit `scope` property were registered
under their real scope but resolved against the parent deployment scope,
so `reference()` on a cross-scope module missed and fell back to a mock.
The resulting unresolved value then reached `guid()` as the final
argument, where the hash was never finalized and expansion failed with
"Hash must be finalized before the hash value is retrieved".

`GetDeploymentScope` now parses the explicit `scope` property for
management group, subscription, resource group, and tenant components.
`GetUnique` now finalizes the hash unconditionally so a non-string final
argument cannot throw.

Fixes Azure#3909

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@oWretch
Jerome Brown (oWretch) requested a review from a team as a code owner September 8, 2026 06:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Expansion fails with "Hash must be finalized before the hash value is retrieved" when using an object output from a cross-scope module

1 participant