From c4229b3cae84d90539e4e2b5ebc8eb56a775ad3a Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 12 Oct 2026 12:14:16 +0000 Subject: [PATCH] ci: three guards on the snapshot prune, and why it is asset by asset The step that removes the assets a snapshot run did not upload now refuses an empty upload (every asset of the release would count as stale), treats an asset that is already gone as removed (one re-read of the release after a failed delete-asset), and reports a failed deletion while trying the rest, failing only afterwards -- the assets of this upload are on the release either way, so this is "attach first, then red" like the signature check. The guards come from the job's own history, dug up from git and the Actions API: its first form (2026-05-07) deleted and recreated the whole snapshot release, and on 2026-05-10 the runs of several main pushes minutes apart deleted it under each other -- every job of each burst but the last failed in that step with its artifacts present. The job went with the Central migration the next morning and came back as view-or-create plus upload --clobber without any cleanup, which is how the backlog grew; no commit named the failures. The step comment, CLAUDE.md and the CHANGELOG bullet say so now, and the runs are recorded in workspace/policies/fat-jar-release-assets.md. The step is the same text as the siblings' shared github-snapshot job. Verified: the script, extracted verbatim from the workflow, against a gh stub in eight cases (empty upload refused with nothing deleted; empty release and equal sets are no-ops; stale assets removed; an asset gone under us tolerated; a real deletion failure reported after the rest were tried; a failed re-read reported; a failed release view fails before anything is deleted), bash -n over every run script, the shared-files and release-gate checks, the buildcheck unit tests. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01AytmJF9faEiQEVt6eetQS2 --- .github/workflows/publish.yml | 39 +++++++++++++++++++++++++++++++++-- CHANGELOG.md | 5 ++++- CLAUDE.md | 13 +++++++----- 3 files changed, 49 insertions(+), 8 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 47ca669f..2b5d87ff 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -3245,23 +3245,58 @@ jobs: # upload on purpose: a failed upload must not leave the release emptier than it was. And only # here: a versioned v* release is history, github-release never deletes anything. Every # deletion is printed by name, so the job log is the audit trail. + # + # Asset by asset, never the release. The first form of this job (2026-05-07) deleted the whole + # `snapshot` release with its tag and recreated it; on 2026-05-10 the runs of several main + # pushes minutes apart deleted it under each other, and every job of each burst but the last + # failed in that step with its artifacts present. The job then went with the Central + # migration and came back as `view || create` + `upload --clobber` without any cleanup, which + # is how the backlog grew (workspace/policies/fat-jar-release-assets.md has the runs). Hence + # three guards: an empty upload is refused (every asset would count as stale; the download + # and upload steps above fail first, the guard keeps it so), an asset that is already gone + # counts as removed (two overlapping dispatches may both pick it), and a failed deletion is + # reported and the rest still tried -- the assets of this upload are on the release either + # way, so this is "attach first, then red" like the signature check. env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -euo pipefail keep="$(ls -1 snapshot-assets | sort)" - current="$(gh release view snapshot --repo ${{ github.repository }} --json assets --jq '.assets[].name' | sort)" + if [ -z "$keep" ]; then + echo "::error::refusing to prune: this run uploaded nothing, so every asset of the release would count as stale" + exit 1 + fi + list_assets() { + gh release view snapshot --repo ${{ github.repository }} --json assets --jq '.assets[].name' | sort + } + current="$(list_assets)" stale="$(comm -13 <(printf '%s\n' "$keep") <(printf '%s\n' "$current"))" if [ -z "$stale" ]; then echo "nothing to remove: the release holds exactly the $(printf '%s\n' "$keep" | wc -l) asset(s) of this upload" exit 0 fi echo "removing $(printf '%s\n' "$stale" | wc -l) asset(s) that are not part of this upload:" + failed=0 while IFS= read -r name; do [ -n "$name" ] || continue echo " deleting $name" - gh release delete-asset snapshot "$name" --repo ${{ github.repository }} --yes + if gh release delete-asset snapshot "$name" --repo ${{ github.repository }} --yes; then + continue + fi + if ! remaining="$(list_assets)"; then + echo "::error::could not delete $name, and could not re-read the release to tell whether it is gone" + failed=$((failed + 1)) + elif printf '%s\n' "$remaining" | grep -qxF -- "$name"; then + echo "::error::could not delete $name" + failed=$((failed + 1)) + else + echo " $name was already gone" + fi done <<< "$stale" + if [ "$failed" -ne 0 ]; then + echo "::error::$failed asset(s) could not be removed; the $(printf '%s\n' "$keep" | wc -l) asset(s) of this upload are on the release" + exit 1 + fi echo "kept the $(printf '%s\n' "$keep" | wc -l) asset(s) of this upload" - name: Fail if anything was attached unsigned # After the upload on purpose: the assets must exist even when the signature does not. diff --git a/CHANGELOG.md b/CHANGELOG.md index 55fb0054..e2ca28ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -500,7 +500,10 @@ from version 5.0.0 onward. Pre-fork releases (`1.x`–`4.2.0`) were authored by name -- `gh release upload --clobber` never removed anything, so the release held 208 assets, 109 of them from 5.0.x and 43 in shapes that no longer exist (`msvc-windows`, `all-*-jar-with-dependencies`), plus three SpotBugs plugin jars an old collect step attached in - 2026-05. Versioned releases are never pruned; the next snapshot run clears the backlog by itself + 2026-05. The prune refuses an empty upload, treats an asset that is already gone as removed and + fails only after trying every deletion (asset by asset on purpose: the job's first form deleted + and recreated the release, and failed in every burst of main pushes in May 2026). Versioned + releases are never pruned; the next snapshot run clears the backlog by itself (`workspace/policies/fat-jar-release-assets.md`). - **A Windows build on an AMX CPU (Sapphire, Emerald or Granite Rapids) crashed the JVM with `STATUS_HEAP_CORRUPTION` when a model was unloaded.** ggml's AMX weight buffer -- used by the diff --git a/CLAUDE.md b/CLAUDE.md index 87eec784..65a8acf6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1048,11 +1048,14 @@ two AARs with the `.aar.asc` Gradle writes next to them, their poms `sign-fatjars.sh`, no `environment:`; `verify-signing-key` still proves the key, and the publish jobs sign with it). Until 2026-10-12 the collect steps copied jars and bare AARs only: every pom was missing, `llama-platform` was absent entirely, the AARs went out unsigned and the `*.jar`-only -signature check never noticed. The check counts jars, AARs and poms now, and `github-snapshot` ends by -deleting every asset of the `snapshot` pre-release the run did not upload, each printed by name -(`gh release upload --clobber` never removed anything: 208 assets, 109 of them from 5.0.x); a versioned -release is never pruned (`workspace/policies/fat-jar-release-assets.md`, "The `snapshot` pre-release -holds exactly the current upload"). +signature check never noticed. The check counts jars, AARs and poms now, and `github-snapshot` ends +by deleting every asset of the `snapshot` pre-release the run did not upload, each printed by name +(`gh release upload --clobber` never removed anything: 208 assets, 109 of them from 5.0.x). The +prune refuses an empty upload, treats an asset that is already gone as removed and fails only after +trying every deletion; a versioned release is never pruned. Why it prunes asset by asset instead of +deleting and recreating the release -- the job's first form, which failed in every burst of main +pushes in May 2026 -- is recorded in `workspace/policies/fat-jar-release-assets.md`, "The `snapshot` +pre-release holds exactly the current upload". The common part of the three smoke scripts is `.github/smoke-lib.sh` (`smoke_classpath`, `smoke_set_modules`, `smoke_assert_loader_line`); the PowerShell script repeats it, since the Windows