mirror of
https://github.com/nexu-io/open-design.git
synced 2026-09-20 06:15:06 +08:00
fc5b276f7d
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.
149 lines
6.2 KiB
YAML
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")
|