From ff283c7a7debd8d0f9e921eaf1f10f6975d678eb Mon Sep 17 00:00:00 2001 From: Chad Bentz <1760475+felickz@users.noreply.github.com> Date: Wed, 28 Oct 2026 09:27:33 -0400 Subject: [PATCH] fix(publish): keep newest result artifact when a publish job is restarted When a publish job is cancelled and restarted, the run ends up with two artifacts with the same name (for example publish-result-csharp-src). The summary job downloaded them with merge-multiple, which extracts every match into one directory in no guaranteed order, so the stale "failed" fragment overwrote the newer "published" one. The release notes then showed "publish failed" for packs that had published (seen on v0.8.3 for the C# and Go query packs). Replace the download step with download-latest-publish-results.sh, which lists the run's artifacts via the API and downloads only the newest non-expired one per name. The summary job needs actions: read for that listing. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../download-latest-publish-results.sh | 41 +++++++++++++++++++ .github/workflows/publish.yml | 12 +++--- 2 files changed, 48 insertions(+), 5 deletions(-) create mode 100755 .github/scripts/download-latest-publish-results.sh diff --git a/.github/scripts/download-latest-publish-results.sh b/.github/scripts/download-latest-publish-results.sh new file mode 100755 index 00000000..deb4fe21 --- /dev/null +++ b/.github/scripts/download-latest-publish-results.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +# Downloads the publish-result-* artifacts of a publish.yml run into a flat +# directory, keeping only the NEWEST artifact per artifact name. +# +# Usage: download-latest-publish-results.sh +# +# Why not actions/download-artifact with merge-multiple? Re-running or +# restarting a publish job (e.g. after cancelling it) uploads a second +# artifact with the same name into the same run, and the earlier attempt's +# artifact is kept. merge-multiple extracts every match into one directory in +# no guaranteed order, so a stale "failed" fragment can overwrite the newer +# "published" one, and the summary then reports a published pack as failed. +# +# Requires: gh (GH_TOKEN with actions:read), jq, unzip. GITHUB_REPOSITORY +# defaults to this repo so the script also works locally. +set -euo pipefail + +RUN_ID="${1:?usage: download-latest-publish-results.sh }" +DEST="${2:?usage: download-latest-publish-results.sh }" +REPO="${GITHUB_REPOSITORY:-GitHubSecurityLab/CodeQL-Community-Packs}" + +mkdir -p "$DEST" +WORK=$(mktemp -d) +trap 'rm -rf "$WORK"' EXIT + +# One " " line per artifact name: the newest non-expired one. +gh api --paginate "repos/${REPO}/actions/runs/${RUN_ID}/artifacts?per_page=100" \ + --jq '.artifacts[] | select((.expired | not) and (.name | startswith("publish-result-"))) | [.created_at, .id, .name] | @tsv' \ + | sort -t$'\t' -k3,3 -k1,1r \ + | awk -F'\t' '!seen[$3]++ { print $2, $3 }' > "$WORK/latest.txt" + +if [ ! -s "$WORK/latest.txt" ]; then + echo "No publish-result-* artifacts found for run ${RUN_ID}." + exit 0 +fi + +while read -r ID NAME; do + echo "Downloading ${NAME} (artifact ${ID})" + gh api "repos/${REPO}/actions/artifacts/${ID}/zip" > "$WORK/${ID}.zip" + unzip -o -q "$WORK/${ID}.zip" -d "$DEST" +done < "$WORK/latest.txt" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 731cc0ad..9078b0ec 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -387,17 +387,19 @@ jobs: permissions: contents: write + actions: read steps: - uses: actions/checkout@v7 + # Not actions/download-artifact with merge-multiple: a re-run or restarted publish job + # leaves a second same-named artifact in the run, and merge-multiple can let the stale + # (e.g. cancelled/failed) one overwrite the newer result. This keeps the newest per name. - name: Download publish results - uses: actions/download-artifact@v8 continue-on-error: true - with: - pattern: publish-result-* - path: publish-results - merge-multiple: true + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: .github/scripts/download-latest-publish-results.sh "${{ github.run_id }}" publish-results - name: Build summary table run: |