Summary
commit-generated-on-merge (in .github/workflows/ValidateSampleDeployments.yml) is gated on head.repo.full_name == github.repository, which means it doesn't run for PRs from forks. This blocks the canonical contributor path: external (and most Microsoft-owned) contributors submit from a fork of Azure/azure-quickstart-templates, not from an upstream branch. When their PR merges, master is left without azuredeploy.json for the changed sample even though /validate already produced a valid one.
The inline comment on the job suggests this guard is intended as belt-and-braces:
Fork PRs cannot reach this workflow at all (the /validate path requires ADX access), so the same-repo guard is defensive belt-and-braces only.
The premise of that comment isn't accurate today. Fork PRs can and do reach /validate — it's issue_comment-triggered, so the workflow runs in the upstream repository's context with upstream secrets and the adx-readonly environment. The validation succeeds and uploads generated-azuredeploy-<pr>-<sha> to upstream's actions storage like any in-repo PR.
Evidence
Concrete example: PR #14762 (from msmbaldwin/azure-quickstart-templates, a fork). The successful /validate run uploaded:
$ gh api "/repos/Azure/azure-quickstart-templates/actions/artifacts?name=generated-azuredeploy-14762-855ec9eaf6cec5e1701a95234eba43396710804c"
{
"id": 6979384358,
"name": "generated-azuredeploy-14762-855ec9eaf6cec5e1701a95234eba43396710804c",
"expired": false,
"size_in_bytes": 4382,
"workflow_run_id": 25820961383,
"created_at": "2026-05-13T19:17:37Z"
}
The artifact is sitting there, valid, with the path structure that #14754 fixed and with the metadata that d1f0fd9 looks up by name. The only thing that prevents commit-generated-on-merge from picking it up on merge is the fork-repo guard.
Impact
For every fork PR that touches a sample template:
- Contributor follows the (current) contribution guide, which says
azuredeploy.json "must not be included in the PR as it will be built automatically when the sample is merged."
/validate succeeds, artifact uploads, maintainer approves and merges.
commit-generated-on-merge is skipped because of the fork gate. Master is left without azuredeploy.json for the sample.
- The sample's "Deploy to Azure" button breaks until someone manually restores the JSON.
This happened on #14762 and triggered the manual-recovery loop in #14764 / #14765. Each fork PR that touches templates will hit it again.
Suggested fix
Drop the fork-repo guard:
commit-generated-on-merge:
name: Commit generated azuredeploy.json on merge
runs-on: ubuntu-latest
if: >-
github.event_name == 'pull_request' &&
github.event.action == 'closed' &&
github.event.pull_request.merged == true &&
- github.event.pull_request.base.ref == github.event.repository.default_branch &&
- github.event.pull_request.head.repo.full_name == github.repository
+ github.event.pull_request.base.ref == github.event.repository.default_branch
Safety analysis
Two concerns worth being explicit about, both of which seem fine to me:
-
Token / push scope. The merge event fires in upstream context, so GITHUB_TOKEN is upstream's repo-scoped token with contents: write. No fork-side credentials are involved at any point. The push to master uses the same identity it does for in-repo PRs today.
-
Content trust. The artifact's azuredeploy.json is produced by selected-pipeline's deterministic bicep build of the PR's main.bicep, then validated against ADX (correlationId + templateHash match against an actual deployment). The trust boundary is maintainer review of the main.bicep change before merge — exactly the same boundary as for in-repo PRs. There's no path for a malicious contributor to substitute a different JSON; the artifact is built by upstream CI from PR content the maintainer has already approved.
If there's a residual concern, an alternative is to require the artifact's workflow_run_id to have been triggered by a MEMBER/OWNER/COLLABORATOR (which /validate already enforces via its commenter check). But I don't think that's necessary — the existing approval-then-merge flow is sufficient.
Related
cc @ouldsid
Summary
commit-generated-on-merge(in.github/workflows/ValidateSampleDeployments.yml) is gated onhead.repo.full_name == github.repository, which means it doesn't run for PRs from forks. This blocks the canonical contributor path: external (and most Microsoft-owned) contributors submit from a fork ofAzure/azure-quickstart-templates, not from an upstream branch. When their PR merges, master is left withoutazuredeploy.jsonfor the changed sample even though/validatealready produced a valid one.The inline comment on the job suggests this guard is intended as belt-and-braces:
The premise of that comment isn't accurate today. Fork PRs can and do reach
/validate— it'sissue_comment-triggered, so the workflow runs in the upstream repository's context with upstream secrets and theadx-readonlyenvironment. The validation succeeds and uploadsgenerated-azuredeploy-<pr>-<sha>to upstream's actions storage like any in-repo PR.Evidence
Concrete example: PR #14762 (from
msmbaldwin/azure-quickstart-templates, a fork). The successful/validaterun uploaded:The artifact is sitting there, valid, with the path structure that #14754 fixed and with the metadata that d1f0fd9 looks up by name. The only thing that prevents
commit-generated-on-mergefrom picking it up on merge is the fork-repo guard.Impact
For every fork PR that touches a sample template:
azuredeploy.json"must not be included in the PR as it will be built automatically when the sample is merged."/validatesucceeds, artifact uploads, maintainer approves and merges.commit-generated-on-mergeis skipped because of the fork gate. Master is left withoutazuredeploy.jsonfor the sample.This happened on #14762 and triggered the manual-recovery loop in #14764 / #14765. Each fork PR that touches templates will hit it again.
Suggested fix
Drop the fork-repo guard:
commit-generated-on-merge: name: Commit generated azuredeploy.json on merge runs-on: ubuntu-latest if: >- github.event_name == 'pull_request' && github.event.action == 'closed' && github.event.pull_request.merged == true && - github.event.pull_request.base.ref == github.event.repository.default_branch && - github.event.pull_request.head.repo.full_name == github.repository + github.event.pull_request.base.ref == github.event.repository.default_branchSafety analysis
Two concerns worth being explicit about, both of which seem fine to me:
Token / push scope. The merge event fires in upstream context, so
GITHUB_TOKENis upstream's repo-scoped token withcontents: write. No fork-side credentials are involved at any point. The push to master uses the same identity it does for in-repo PRs today.Content trust. The artifact's
azuredeploy.jsonis produced byselected-pipeline's deterministicbicep buildof the PR'smain.bicep, then validated against ADX (correlationId + templateHash match against an actual deployment). The trust boundary is maintainer review of themain.bicepchange before merge — exactly the same boundary as for in-repo PRs. There's no path for a malicious contributor to substitute a different JSON; the artifact is built by upstream CI from PR content the maintainer has already approved.If there's a residual concern, an alternative is to require the artifact's
workflow_run_idto have been triggered by a MEMBER/OWNER/COLLABORATOR (which/validatealready enforces via its commenter check). But I don't think that's necessary — the existing approval-then-merge flow is sufficient.Related
/validatepath works end-to-end for fork PRs; the fork gate is the last barrier to a clean fork → merge flow.cc @ouldsid