Summary
The commit-generated-on-merge job in ValidateSampleDeployments.yml will commit the generated azuredeploy.json to the repo root instead of the sample's folder. This happens because actions/upload-artifact@v4 strips the common path prefix when only one of the listed paths actually exists, producing a flat-rooted zip.
Found by inspecting the artifact uploaded by PR #14752 (the canary for the new pipeline). The canary has not been merged yet because this bug would corrupt master.
Repro
PR #14752, /validate run 25772629137 succeeded with all checks green. The artifact generated-azuredeploy-14752-9ac735dee3bdfc1e7b4f75741b7aba94e7b94aa8 was uploaded successfully. Its zip contents:
$ unzip -l artifact.zip
Length Name
3989 azuredeploy.json
The file is at the zip root, not under quickstarts/microsoft.keyvault/key-vault-create/.
Why it happens
upload-artifact@v4 strips the common path prefix:
- name: Upload generated azuredeploy.json artifact
uses: actions/upload-artifact@v4
with:
name: generated-azuredeploy-${{ steps.pr.outputs.number }}-${{ steps.pr.outputs.head_sha }}
path: |
${{ steps.find-metadata.outputs.sample_path }}/azuredeploy.json
${{ steps.find-metadata.outputs.sample_path }}/prereqs/azuredeploy.json
${{ steps.find-metadata.outputs.sample_path }}/prereqs/prereq.azuredeploy.json
When the sample has no prereqs/ (as with quickstarts/microsoft.keyvault/key-vault-create), only the first path matches → upload-artifact treats quickstarts/microsoft.keyvault/key-vault-create/ as the common ancestor → strips that prefix → zip contains just azuredeploy.json.
The commit-generated-on-merge job then:
- name: Download generated azuredeploy.json
uses: actions/download-artifact@v4
with:
name: ${{ steps.locate-artifact.outputs.name }}
path: .
...
- name: Commit and push generated azuredeploy.json
run: |
git add -A -- '**/azuredeploy.json' '**/prereq.azuredeploy.json' || true
git commit -m "Auto-add generated azuredeploy.json from PR #${PR_NUM}"
git push origin "HEAD:${DEFAULT_BRANCH}"
Extracts to . → ./azuredeploy.json at repo root. git add -A -- '**/azuredeploy.json' happily stages it from the root. Pushed to master.
Net effect: master would get a phantom /azuredeploy.json at the repo root, not the file in the correct sample folder. The sample stays broken.
Suggested fixes
- Pass
include-hidden-files: false won't help — that's about dotfiles, not path-stripping.
- Use
include-hidden-files is the wrong knob. The actual upload-artifact@v4 control for path preservation is path shape. Listing the sample's root folder and filtering excludes the prefix-stripping behavior.
The simplest fix is to upload with the full repo-relative path always preserved. One reliable pattern:
- name: Upload generated azuredeploy.json artifact
uses: actions/upload-artifact@v4
with:
name: generated-azuredeploy-${{ steps.pr.outputs.number }}-${{ steps.pr.outputs.head_sha }}
path: |
${{ steps.find-metadata.outputs.sample_path }}/azuredeploy.json
${{ steps.find-metadata.outputs.sample_path }}/prereqs/azuredeploy.json
${{ steps.find-metadata.outputs.sample_path }}/prereqs/prereq.azuredeploy.json
if-no-files-found: ignore
include-hidden-files: false
# Force preservation of full path structure:
overwrite: true
Actually, upload-artifact@v4 does not provide a "preserve full path" flag. The fix that works: write the file to a known-safe relative location (e.g., create a generated/ dir, copy with full path preserved, upload generated/**), and adjust the download path accordingly. Pseudocode:
- name: Stage artifact tree
run: |
mkdir -p "$RUNNER_TEMP/artifact-root"
cp --parents "${{ steps.find-metadata.outputs.sample_path }}/azuredeploy.json" "$RUNNER_TEMP/artifact-root/"
# ... same for prereqs files if they exist
- uses: actions/upload-artifact@v4
with:
name: generated-azuredeploy-${{ steps.pr.outputs.number }}-${{ steps.pr.outputs.head_sha }}
path: ${{ runner.temp }}/artifact-root/
Then on download, extract to . and the full quickstarts/.../azuredeploy.json structure is preserved.
Alternatively, encode the sample path in the artifact name and reconstruct it in the download job (less elegant).
Severity
This is a silent corruption bug: every PR that successfully passes /validate and gets merged today commits azuredeploy.json to the wrong location in master. The sample stays broken (no JSON in the sample folder), and master accumulates phantom root-level azuredeploy.json files.
I don't think any PR has actually hit this in the wild yet because the only PR I'm aware of that exercised the new flow end-to-end is #14752 (this canary), and I noticed the issue before merging. But anyone who runs /validate on a bicep-only PR going forward will trigger this.
Recommendation
Hold all bicep-only PRs that would rely on commit-generated-on-merge until this is fixed. Contributors should explicitly commit azuredeploy.json in their PR for now (per the older 1-CONTRIBUTION-GUIDE/README.md guidance, which is also stale per [a separate doc-drift issue I'm filing].
cc @ouldsid
Related: #14753 (CI bicep version mismatch causing templateHash failures)
Summary
The
commit-generated-on-mergejob inValidateSampleDeployments.ymlwill commit the generatedazuredeploy.jsonto the repo root instead of the sample's folder. This happens becauseactions/upload-artifact@v4strips the common path prefix when only one of the listed paths actually exists, producing a flat-rooted zip.Found by inspecting the artifact uploaded by PR #14752 (the canary for the new pipeline). The canary has not been merged yet because this bug would corrupt master.
Repro
PR #14752,
/validaterun 25772629137 succeeded with all checks green. The artifactgenerated-azuredeploy-14752-9ac735dee3bdfc1e7b4f75741b7aba94e7b94aa8was uploaded successfully. Its zip contents:The file is at the zip root, not under
quickstarts/microsoft.keyvault/key-vault-create/.Why it happens
upload-artifact@v4strips the common path prefix:When the sample has no
prereqs/(as withquickstarts/microsoft.keyvault/key-vault-create), only the first path matches → upload-artifact treatsquickstarts/microsoft.keyvault/key-vault-create/as the common ancestor → strips that prefix → zip contains justazuredeploy.json.The
commit-generated-on-mergejob then:Extracts to
.→./azuredeploy.jsonat repo root.git add -A -- '**/azuredeploy.json'happily stages it from the root. Pushed to master.Net effect: master would get a phantom
/azuredeploy.jsonat the repo root, not the file in the correct sample folder. The sample stays broken.Suggested fixes
include-hidden-files: falsewon't help — that's about dotfiles, not path-stripping.include-hidden-filesis the wrong knob. The actual upload-artifact@v4 control for path preservation ispathshape. Listing the sample's root folder and filtering excludes the prefix-stripping behavior.The simplest fix is to upload with the full repo-relative path always preserved. One reliable pattern:
Actually,
upload-artifact@v4does not provide a "preserve full path" flag. The fix that works: write the file to a known-safe relative location (e.g., create agenerated/dir, copy with full path preserved, uploadgenerated/**), and adjust the download path accordingly. Pseudocode:Then on download, extract to
.and the fullquickstarts/.../azuredeploy.jsonstructure is preserved.Alternatively, encode the sample path in the artifact name and reconstruct it in the download job (less elegant).
Severity
This is a silent corruption bug: every PR that successfully passes
/validateand gets merged today commitsazuredeploy.jsonto the wrong location in master. The sample stays broken (no JSON in the sample folder), and master accumulates phantom root-levelazuredeploy.jsonfiles.I don't think any PR has actually hit this in the wild yet because the only PR I'm aware of that exercised the new flow end-to-end is #14752 (this canary), and I noticed the issue before merging. But anyone who runs
/validateon a bicep-only PR going forward will trigger this.Recommendation
Hold all bicep-only PRs that would rely on
commit-generated-on-mergeuntil this is fixed. Contributors should explicitly commitazuredeploy.jsonin their PR for now (per the older1-CONTRIBUTION-GUIDE/README.mdguidance, which is also stale per [a separate doc-drift issue I'm filing].cc @ouldsid
Related: #14753 (CI bicep version mismatch causing templateHash failures)