Files
nexu-io__open-design/.github/workflows/comment.atom.yml
lefarcen fc5b276f7d feat(ci): surface merge-queue needs-validation ejection as a PR comment (#5519)
A PR ejected from the merge queue by the needs-validation gate leaves no
trace on the PR itself: the gate fails on the queue's transient ref, the
PR's own checks stay green, and mergeQueueEntry silently goes null. The
only way to learn why the PR fell out of the queue is to dig through
merge_group workflow runs, which nobody watches (observed on #5329).

Producer: the merge_group gate in ci.yml now emits a handoff/comment
artifact targeting the labeled PR right before it blocks, and uploads it
on the failure path. Consumer: comment.atom.yml accepts merge_group
workflow_run events; since a merge_group run's head_sha is the queue's
synthetic merge commit, artifacts from those runs are bound to their
producing run by run_id, and the base-freshness skip is waived (PRs
ahead in the queue move the base while the run completes). The live-PR
head check still guards staleness.

Topology coverage pins the new boundary: comment.atom consumes
pull_request + merge_group; autofix/report stay pull_request-only.
2026-07-13 04:56:12 +00:00

149 lines
6.2 KiB
YAML

name: comment.atom
on:
workflow_run:
workflows: [ci]
types: [completed]
permissions:
actions: read
contents: read
issues: write
pull-requests: write
concurrency:
group: comment-${{ github.event.workflow_run.id }}
cancel-in-progress: false
jobs:
comment:
name: Publish handoff comments
# merge_group is consumed alongside pull_request so the needs-validation gate can leave an
# ejection notice on the queued PR (the gate failure itself is invisible from the PR head).
if: ${{ github.repository == 'nexu-io/open-design' && (github.event.workflow_run.event == 'pull_request' || github.event.workflow_run.event == 'merge_group') }}
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- name: Checkout trusted workflow code
uses: actions/checkout@v6.0.2
with:
repository: ${{ github.repository }}
ref: ${{ github.event.repository.default_branch }}
- name: Check handoff helper
run: python3 .github/scripts/handoff.py self-check
- name: Resolve comment handoff artifacts
id: artifacts
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
RUN_ID: ${{ github.event.workflow_run.id }}
shell: bash
run: |
set -euo pipefail
pattern="$(python3 .github/scripts/handoff.py artifact-pattern comment)"
count="$(
gh api "repos/$REPO/actions/runs/$RUN_ID/artifacts" \
--jq '.artifacts[]? | select(.expired == false and (.name | startswith("handoff-comment-"))) | .name' \
| sed '/^$/d' \
| wc -l \
| tr -d ' '
)"
if [ "$count" = "0" ]; then
echo "found=false" >> "$GITHUB_OUTPUT"
exit 0
fi
{
echo "found=true"
echo "pattern=$pattern"
} >> "$GITHUB_OUTPUT"
- name: Download comment handoff artifacts
if: ${{ steps.artifacts.outputs.found == 'true' }}
uses: actions/download-artifact@v8
with:
pattern: ${{ steps.artifacts.outputs.pattern }}
run-id: ${{ github.event.workflow_run.id }}
path: ${{ runner.temp }}/handoff-comment
merge-multiple: false
github-token: ${{ github.token }}
- name: Upsert handoff comments
if: ${{ steps.artifacts.outputs.found == 'true' }}
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
RUN_HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
RUN_EVENT: ${{ github.event.workflow_run.event }}
RUN_ID: ${{ github.event.workflow_run.id }}
shell: bash
run: |
set -euo pipefail
root="$RUNNER_TEMP/handoff-comment"
helper=".github/scripts/handoff.py"
while IFS= read -r entry_json; do
id="$(jq -r '.id' <<< "$entry_json")"
pr_number="$(jq -r '.pr_number' <<< "$entry_json")"
head_sha="$(jq -r '.head_sha' <<< "$entry_json")"
base_sha="$(jq -r '.base_sha' <<< "$entry_json")"
marker="$(jq -r '.marker' <<< "$entry_json")"
body_path="$(jq -r '.body_path' <<< "$entry_json")"
if [ "$RUN_EVENT" = "merge_group" ]; then
# A merge_group run's head_sha is the queue's synthetic merge commit, never the PR
# head, so bind the artifact to its producing run by run_id instead; the live-PR
# head check below still carries the staleness guarantee.
artifact_run_id="$(jq -r '.run_id' <<< "$entry_json")"
if [ "$artifact_run_id" != "$RUN_ID" ]; then
echo "Skipping comment handoff $id because artifact run $artifact_run_id does not match workflow_run $RUN_ID."
continue
fi
elif [ "$head_sha" != "$RUN_HEAD_SHA" ]; then
echo "Skipping comment handoff $id because artifact head $head_sha does not match workflow_run head $RUN_HEAD_SHA."
continue
fi
pr_json="$(gh api "repos/$REPO/pulls/$pr_number")"
pr_state="$(jq -r '.state' <<< "$pr_json")"
pr_draft="$(jq -r '.draft' <<< "$pr_json")"
current_head="$(jq -r '.head.sha' <<< "$pr_json")"
current_base="$(jq -r '.base.sha' <<< "$pr_json")"
if [ "$pr_state" != "open" ]; then
echo "Skipping comment handoff $id because PR $pr_number state is $pr_state."
continue
fi
if [ "$pr_draft" != "false" ]; then
echo "Skipping comment handoff $id because PR $pr_number is draft."
continue
fi
if [ "$current_head" != "$head_sha" ]; then
echo "Skipping stale comment handoff $id for $head_sha; current PR head is $current_head."
continue
fi
# Base freshness is meaningless for a merge-queue ejection notice: PRs ahead in the
# queue merge into the base branch while this run completes, so the base is EXPECTED
# to move; the notice stays valid as long as the PR head is unchanged (checked above).
if [ "$RUN_EVENT" != "merge_group" ] && [ "$current_base" != "$base_sha" ]; then
echo "Skipping stale comment handoff $id for base $base_sha; current PR base is $current_base."
continue
fi
payload_file="$RUNNER_TEMP/comment-$id.json"
jq -n --rawfile body "$body_path" '{body: $body}' > "$payload_file"
comment_id="$(
gh api --paginate "repos/$REPO/issues/$pr_number/comments" \
| jq -r --arg marker "$marker" '.[] | select((.body // "") | contains($marker)) | .id' \
| tail -n 1 || true
)"
if [ -n "$comment_id" ]; then
gh api --method PATCH "repos/$REPO/issues/comments/$comment_id" --input "$payload_file" >/dev/null
echo "Updated comment handoff $id on PR $pr_number."
else
gh api --method POST "repos/$REPO/issues/$pr_number/comments" --input "$payload_file" >/dev/null
echo "Created comment handoff $id on PR $pr_number."
fi
done < <(python3 "$helper" list comment "$root")