From 0f601ee1de0f573cbf969079b97c16e33474cd64 Mon Sep 17 00:00:00 2001 From: bigboateng Date: Sun, 26 Jul 2026 16:48:48 +0100 Subject: [PATCH] ci: auto-close superseded sync PRs after a sync merges auto-merge-sync merges a passing sync PR but never closed the older sync PRs it superseded. A sync PR that fails CI is never merged by this job, so once a newer sync overtakes it, it lingers as an orphan (this is what left #109/#113/#114 open and failing until they were closed by hand). After the merge, check out the public intelligence-flow repo for history and close every open sync/intelligence-flow-* PR whose source commit is an ancestor of the just-merged source (already included), skipping the merged branch itself and leaving newer, not-yet-merged syncs untouched. Reuses the primitives already in the pipeline: gh pr list --json, git merge-base --is-ancestor, the sync branch prefix, and UPSTREAM.json's source.commit. --- .github/workflows/ci.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 738a815..967269d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -204,3 +204,38 @@ jobs: GH_TOKEN: ${{ steps.app-token.outputs.token }} PR_URL: ${{ github.event.pull_request.html_url }} run: gh pr merge "$PR_URL" --squash + # After a sync lands, close any older sync PR it supersedes. A sync PR that + # failed CI is never merged by this job, so without this it lingers as an + # orphan once a newer sync overtakes it (see the pile-up that motivated this). + # intelligence-flow is public, so the default token can read its history for + # the ancestry check; closing uses the app token (pull-requests: write). + - name: Check out Intelligence Flow for ancestry + uses: actions/checkout@v4 + with: + repository: operatorstack/intelligence-flow + fetch-depth: 0 + path: intelligence-flow + - name: Close superseded sync PRs + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + shell: bash + run: | + merged="$(jq -r '.source.commit' UPSTREAM.json)" + if [[ -z "$merged" || "$merged" == "null" ]]; then + echo "No recorded source commit; skipping supersession cleanup." + exit 0 + fi + gh pr list --state open --json number,headRefName \ + --jq '.[] | select(.headRefName | startswith("sync/intelligence-flow-")) | [.number, .headRefName] | @tsv' \ + | while IFS=$'\t' read -r number branch; do + short="${branch#sync/intelligence-flow-}" + # Skip the sync that just merged (its own branch), not a supersession. + [[ "${merged:0:12}" == "$short" ]] && continue + # Close only PRs whose source is an ancestor of the merged source — + # i.e. already included. A newer, not-yet-merged sync (descendant) is + # left untouched. + if git -C intelligence-flow merge-base --is-ancestor "$short" "$merged" 2>/dev/null; then + echo "Closing superseded sync PR #$number ($short)." + gh pr close "$number" --comment "Superseded by the sync at ${merged:0:12}, which already includes this PR's source ($short). Closing the stale sync PR automatically." + fi + done