forked from molecule-ai/molecule-core
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 6. - [Release notes](https://github.com/actions/checkout/releases) - [Commits](https://github.com/actions/checkout/compare/v4...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com>
124 lines
5.4 KiB
YAML
124 lines
5.4 KiB
YAML
name: Check merge_group trigger on required workflows
|
|
|
|
# Pre-merge guard against the deadlock pattern where a workflow whose
|
|
# check is in `required_status_checks` lacks a `merge_group:` trigger.
|
|
# Without it, GitHub merge queue stalls forever in AWAITING_CHECKS
|
|
# because the required check can't fire on `gh-readonly-queue/...` refs.
|
|
#
|
|
# This workflow:
|
|
# 1. Lists required status checks on the branch protection rule for `staging`
|
|
# 2. For each required check, finds the workflow that produces it (by job
|
|
# name match)
|
|
# 3. Fails if any such workflow lacks `merge_group:` in its triggers
|
|
#
|
|
# Reasoning for staging-only: main has its own CI gating model (PR review),
|
|
# but staging is what the merge queue runs on, so it's the trigger that
|
|
# matters.
|
|
|
|
on:
|
|
pull_request:
|
|
paths:
|
|
- '.github/workflows/**.yml'
|
|
- '.github/workflows/**.yaml'
|
|
push:
|
|
branches: [staging, main]
|
|
paths:
|
|
- '.github/workflows/**.yml'
|
|
- '.github/workflows/**.yaml'
|
|
# Self-listen on merge_group so the linter passes its own queue run.
|
|
merge_group:
|
|
types: [checks_requested]
|
|
|
|
jobs:
|
|
check:
|
|
name: Required workflows have merge_group trigger
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: read
|
|
steps:
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
- name: Verify merge_group trigger on required-check workflows
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
REPO: ${{ github.repository }}
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
# Branch we care about — the one merge queue runs on.
|
|
BRANCH=staging
|
|
|
|
# Pull the list of required status check contexts. If the branch
|
|
# has no protection or no required checks, exit clean — nothing
|
|
# to lint.
|
|
REQUIRED=$(gh api "repos/${REPO}/branches/${BRANCH}/protection/required_status_checks" \
|
|
--jq '.contexts[]' 2>/dev/null || true)
|
|
if [ -z "$REQUIRED" ]; then
|
|
echo "No required status checks on ${BRANCH} — nothing to verify."
|
|
exit 0
|
|
fi
|
|
|
|
echo "Required checks on ${BRANCH}:"
|
|
echo "${REQUIRED}" | sed 's/^/ - /'
|
|
echo
|
|
|
|
# Build a map: workflow file -> set of job names declared in it.
|
|
# We use yq if available, otherwise grep the `name:` lines under
|
|
# `jobs:`. Stick with grep for portability — runner image always
|
|
# has it; yq isn't in the default image as of 2026-04.
|
|
declare -A workflow_jobs
|
|
shopt -s nullglob
|
|
for wf in .github/workflows/*.yml .github/workflows/*.yaml; do
|
|
[ -f "$wf" ] || continue
|
|
# Extract the workflow name (the `name:` at file root).
|
|
wf_name=$(awk '/^name:[[:space:]]/ {sub(/^name:[[:space:]]+/,""); gsub(/^"|"$/,""); print; exit}' "$wf")
|
|
# Extract job step names from the `jobs:` block. A job step is:
|
|
# - id under `jobs:` (key with 2-space indent followed by colon)
|
|
# - the `name:` field inside that job (4-space indent)
|
|
# We collect both because required_status_checks contexts can
|
|
# match either, depending on how the workflow was authored.
|
|
jobs_block=$(awk '/^jobs:/{flag=1; next} flag' "$wf")
|
|
job_names=$(echo "$jobs_block" | awk '/^[[:space:]]{4}name:[[:space:]]/ {sub(/^[[:space:]]+name:[[:space:]]+/,""); gsub(/^["'"'"']|["'"'"']$/,""); print}')
|
|
workflow_jobs["$wf"]="${wf_name}"$'\n'"${job_names}"
|
|
done
|
|
|
|
# For each required check, find the workflow that produces it.
|
|
# Then verify that workflow lists merge_group as a trigger.
|
|
FAILED=0
|
|
while IFS= read -r check; do
|
|
[ -z "$check" ] && continue
|
|
owning_wf=""
|
|
for wf in "${!workflow_jobs[@]}"; do
|
|
if echo "${workflow_jobs[$wf]}" | grep -Fxq "$check"; then
|
|
owning_wf="$wf"
|
|
break
|
|
fi
|
|
done
|
|
|
|
if [ -z "$owning_wf" ]; then
|
|
echo "::warning::Required check '${check}' has no matching workflow in this repo. Skipping (may be from an external app)."
|
|
continue
|
|
fi
|
|
|
|
# Does the workflow's trigger list include merge_group?
|
|
# Match either bare `merge_group:` line or merge_group with
|
|
# subsequent indented config (types: [checks_requested]).
|
|
if grep -qE '^[[:space:]]*merge_group:' "$owning_wf"; then
|
|
echo "OK: '${check}' (in $owning_wf) — has merge_group trigger"
|
|
else
|
|
echo "::error file=${owning_wf}::Required check '${check}' is produced by ${owning_wf}, but the workflow does not declare a 'merge_group:' trigger. With merge queue enabled on ${BRANCH}, this will deadlock the queue (every PR sits AWAITING_CHECKS forever). Add this to the workflow's 'on:' block:"
|
|
echo "::error file=${owning_wf}:: merge_group:"
|
|
echo "::error file=${owning_wf}:: types: [checks_requested]"
|
|
FAILED=1
|
|
fi
|
|
done <<< "$REQUIRED"
|
|
|
|
if [ "$FAILED" -ne 0 ]; then
|
|
echo
|
|
echo "::error::Block. See errors above. Reference: $(grep -l 'reference_merge_queue' /dev/null 2>/dev/null || echo 'memory: reference_merge_queue_enablement.md')."
|
|
exit 1
|
|
fi
|
|
|
|
echo
|
|
echo "All required workflows on ${BRANCH} declare merge_group triggers."
|