Master Merge Guard Workflow
Developer documentation for the Master merge guard GitHub Actions workflow.
| Item | Value |
|---|---|
| Workflow file | .github/workflows/master-guard.yml |
| Workflow name (Actions UI) | Master merge guard |
| Job name (status check) | Allowed source branch |
| Full check name (typical) | Master merge guard / Allowed source branch |
| Runner | ubuntu-latest |
| Permissions | contents: read |
| Related workflow | Branch promotion guard |
Table of contents
- Purpose
- Role in the branch promotion model
- What this workflow enforces
- What this workflow does not enforce
- Architecture overview
- Triggers and events
- Validation logic (step by step)
- Repository ruleset configuration
- Dependabot integration
- Typical developer workflows
- Interaction with other CI workflows
- Security model
- Decision matrix
- Troubleshooting
- Operational procedures
- Maintaining and extending the workflow
Purpose
The Master merge guard ensures that master receives changes only through controlled promotion paths:
- Pull requests whose head branch is
gold(release-candidate line promoted fromcanary). - Pull requests opened by Dependabot against
master(head branches matchingdependabot/*).
The workflow exists to protect the stable production line from accidental merges of feature branches, pre-release branches (alpha, canary), or fork-based contributions directly into master.
master triggers stable releases via release.yml (release-master job) and is the target of Dependabot updates in .github/dependabot.yml. Guarding merge sources reduces the risk of publishing unintended builds to NuGet.org.
Role in the branch promotion model
This repository uses a sequential promotion chain for code moving toward production:
feature/* ──PR──> alpha ──PR──> canary ──PR──> gold ──PR──> master
│ │ │ │
│ │ │ └── Master merge guard (this doc)
│ │ └── Branch promotion guard (canary -> gold)
│ └── Branch promotion guard (alpha -> canary)
└── Feature PRs (not restricted by promotion guards)
| Stage | Branch | Guard workflow | Allowed PR sources into target |
|---|---|---|---|
| Development integration | alpha |
(none from this family) | Feature branches, etc. |
| Pre-release testing | canary |
Branch promotion guard | alpha only |
| Release candidate | gold |
Branch promotion guard | canary only |
| Stable production | master |
Master merge guard (this workflow) | gold, dependabot/* |
See Branch promotion guard for the alpha → canary → gold rules.
What this workflow enforces
When a pull request targets master, the workflow:
- Rejects fork PRs — the head repository must be
Krypton-Suite/Standard-Toolkit(same repo as the base). - Allows
gold→master— head ref must be exactlygold. - Allows Dependabot →
master— head ref must match the patterndependabot/*(e.g.dependabot/github_actions/actions/checkout-6).
If validation passes, the job succeeds and reports a green status check. If validation fails, the job fails with a GitHub Actions error annotation (::error::).
What this workflow does not enforce
| Scenario | Why it is not blocked by this workflow alone | Required mitigation |
|---|---|---|
Direct push to master |
Actions run after the push; they cannot reject it | Repository ruleset: restrict updates |
| Opening a PR from a disallowed branch | GitHub always allows opening PRs | Failed status check blocks merge when ruleset requires this check |
Force-push to master |
Not validated by this workflow | Ruleset: block force pushes |
| Admin bypass of branch rules | Workflow has no admin override | Ruleset: apply to administrators |
| Merging without other CI | This guard only validates source branch | Keep existing required checks (Build, CodeQL, etc.) |
PRs into alpha, canary, gold |
Out of scope | Branch promotion guard |
Architecture overview
flowchart TD
subgraph trigger [Pull request event]
PR[PR targets master]
end
subgraph workflow [master-guard.yml]
FORK{Head repo == this repo?}
GOLD{Head ref == gold?}
DEPS{Head ref matches dependabot/*?}
PASS[Job succeeds]
FAIL[Job fails - merge blocked if check required]
end
subgraph ruleset [Repository ruleset on master]
RP[Require PR + status checks]
BLOCK[Block direct push]
end
PR --> FORK
FORK -->|No| FAIL
FORK -->|Yes| GOLD
GOLD -->|Yes| PASS
GOLD -->|No| DEPS
DEPS -->|Yes| PASS
DEPS -->|No| FAIL
PASS --> RP
FAIL --> RP
BLOCK -.->|prevents| PR
Triggers and events
Defined in master-guard.yml:
on:
pull_request:
branches:
- master
types:
- opened
- synchronize
- reopened
- edited
| Event type | When it fires | Why it matters |
|---|---|---|
opened |
New PR targeting master |
Initial validation |
synchronize |
New commits pushed to PR head branch | Re-validate after updates |
reopened |
Closed PR reopened | Re-validate |
edited |
PR base/head changed | Re-validate if target or source branch changes |
The workflow does not run on:
pushtomaster(cannot block pushes anyway)pull_request_target(not needed; no write permissions or secrets required)workflow_dispatch(manual runs are unnecessary for policy checks)
Validation logic (step by step)
The single job allowed-source-branch runs on ubuntu-latest and executes a Bash script.
Step 1 — Resolve PR metadata
| Variable | Source | Example |
|---|---|---|
head_repo |
github.event.pull_request.head.repo.full_name |
Krypton-Suite/Standard-Toolkit |
head_ref |
github.event.pull_request.head.ref |
gold or dependabot/github_actions/actions/checkout-6 |
this_repo |
github.repository |
Krypton-Suite/Standard-Toolkit |
Step 2 — Same-repository check
if [ "$head_repo" != "$this_repo" ]; then
exit 1
fi
Fork PRs fail here. Dependabot and in-repo promotion PRs pass.
Step 3 — Allowed source branch check
Path A — gold:
if [ "$head_ref" = "gold" ]; then
exit 0
fi
Branch name comparison is case-sensitive. The remote branch is gold (lowercase).
Path B — Dependabot:
case "$head_ref" in
dependabot/*)
exit 0
;;
esac
Matches all Dependabot branch prefixes used for GitHub Actions updates (see Dependabot integration).
Path C — anything else:
echo "::error::Pull requests to master are only allowed from branch 'gold' or Dependabot branches (got '$head_ref')."
exit 1
Repository ruleset configuration
This workflow alone only adds a status check. Direct pushes to master must be blocked with a GitHub ruleset (or classic branch protection).
Recommended ruleset: Protect master
Location: GitHub → Repository → Settings → Rules → Rulesets → New ruleset
| Setting | Value |
|---|---|
| Ruleset name | Protect master |
| Enforcement status | Active |
| Target branches | Include by name → master |
| Restrict updates | Enabled (blocks direct pushes) |
| Require a pull request before merging | Enabled |
| Require status checks to pass | Enabled — add Master merge guard / Allowed source branch (exact label may vary slightly in the UI) |
| Block force pushes | Enabled |
| Apply to administrators | Recommended: Enabled |
| Bypass list | Only trusted automation accounts (if any must push directly) |
Minimum required status checks (suggested)
Combine this guard with existing CI:
| Check | Purpose |
|---|---|
Master merge guard / Allowed source branch |
Source branch policy |
| Build workflow job(s) | Compile all TFMs |
CodeQL (if required on master) |
Security analysis |
| Other team-required reviews | Human approval |
Bootstrapping (first-time enablement)
- Merge
master-guard.ymlontomaster(may require a one-time admin merge or ruleset bypass). - Open a test PR from
gold→masterand confirm the check appears and passes. - Open a test PR from another branch (e.g.
alpha→master) and confirm the check fails. - Enable the ruleset with Require status checks once the check name is visible in the ruleset UI.
- Enable Restrict updates to block direct pushes.
Dependabot integration
Dependabot is configured in .github/dependabot.yml:
package-ecosystem: "github-actions"
target-branch: "master"
Dependabot opens PRs with head branches such as:
dependabot/github_actions/actions/checkout-6dependabot/github_actions/actions/setup-dotnet-5
The master merge guard explicitly allows any head ref matching dependabot/* so weekly GitHub Actions dependency updates can merge into master without going through gold.
Assignee behaviour: .github/workflows/auto-assign-pr-author.yml assigns Dependabot PRs to the repository owner instead of dependabot[bot].
Review policy: Dependabot PRs still require normal review/CI rules configured on the ruleset; this guard only validates the source branch name.
Typical developer workflows
Promoting a stable release to master
- Ensure changes have flowed
alpha→canary→gold(see Branch promotion guard). - Open a PR: base
master, comparegold. - Wait for Master merge guard / Allowed source branch and other CI checks.
- Merge after approval.
release-masterinrelease.ymlruns on push tomaster.
Merging a Dependabot update
- Dependabot opens
dependabot/*→master. - Master merge guard passes automatically.
- Review and merge like any other PR.
What not to do
| Action | Result |
|---|---|
PR feature/my-fix → master |
Guard fails |
PR alpha → master |
Guard fails |
PR canary → master |
Guard fails |
PR from a fork → master |
Guard fails |
Direct git push origin master |
Blocked by ruleset (once configured), not by workflow |
Interaction with other CI workflows
| Workflow | Relationship |
|---|---|
build.yml |
Runs on PRs to all branches including master; independent of source-branch policy |
release.yml |
Runs on push to master after merge |
codeql.yml |
Analyzes PRs targeting master |
repo-mirror.yml |
Mirrors master on push |
dependabot.yml |
Targets master; allowed by this guard |
| Branch promotion guard | Upstream policy for gold content |
Promotion guards and build workflows are complementary: build verifies quality; merge guards verify provenance.
Security model
| Topic | Design choice |
|---|---|
pull_request vs pull_request_target |
Uses pull_request — untrusted fork code is not checked out; only metadata is read |
| Permissions | Minimal (contents: read) |
| Secrets | None required |
| Fork exclusion | Prevents drive-by PRs from external forks into master |
| Dependabot allowance | Dependabot branches are always in-repo; pattern match is safe |
This workflow is policy enforcement, not code scanning. Malicious code could still exist on gold; review and CI remain essential.
Decision matrix
| Base branch | Head branch | Head repo | Guard result |
|---|---|---|---|
master |
gold |
Same repo | Pass |
master |
dependabot/github_actions/... |
Same repo | Pass |
master |
alpha |
Same repo | Fail |
master |
canary |
Same repo | Fail |
master |
feature/* |
Same repo | Fail |
master |
gold |
Fork | Fail |
master |
any | Fork | Fail |
Troubleshooting
Check does not appear on PR
- Confirm
master-guard.ymlexists on the default branch or the PR’s base branch. - Confirm the PR targets
master(notmainor another name). - Confirm Actions are enabled for the repository.
- Re-run checks from the PR Checks tab if the workflow was added after PR creation (close/reopen or push an empty commit).
Check fails for a valid gold → master PR
- Verify head branch is exactly
gold(case-sensitive). - Verify the PR is from this repository, not a fork.
- Read the job log for the exact
head_refvalue.
Check fails for Dependabot PR
- Confirm head branch starts with
dependabot/(Dependabot version updates use this prefix). - If using a different bot or prefix, extend the workflow
casepattern.
Merge button disabled despite green guard
Other required checks or reviews may be pending. This guard is one of several gates.
Direct push to master still works
The ruleset is missing or bypassed. Enable Restrict updates and Apply to administrators.
Dependabot PR passes guard but team wants it on gold instead
Change target-branch in dependabot.yml to gold and remove the Dependabot exemption from this workflow. That is a policy change, not a bug.
Operational procedures
Temporarily allowing an exception merge
Prefer ruleset bypass for a trusted maintainer with audit trail, rather than disabling the workflow.
- Add maintainer to ruleset bypass list (time-limited).
- Perform merge.
- Remove bypass entry.
Disabling the workflow file entirely affects all PRs to master.
Renaming the gold branch
Update:
master-guard.yml— allowed head ref- Branch promotion guard —
goldbase andcanaryhead rules - All workflow
push/pull_requestbranch lists - Rulesets and mirror configuration
Verifying policy after changes
gold→master: expect passalpha→master: expect faildependabot/*→master: expect pass- Fork →
master: expect fail
Maintaining and extending the workflow
When editing master-guard.yml:
- Update this documentation and GitHub Workflow Index.
- Keep header comments in the YAML file aligned with ruleset steps.
- If adding new allowed sources (e.g. release bot branch), document the security rationale.
- Avoid
pull_request_targetunless you need elevated permissions and understand the security implications. - Test with real PRs before marking the check as required in rulesets.
Related files: