From bdb4434110424a606a7234b460f239300f47b6e5 Mon Sep 17 00:00:00 2001 From: TonyTonyCoder11 Date: Fri, 31 Jul 2026 16:46:58 +0200 Subject: [PATCH] Stop a metadata step from taking down a finished release MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The v2.0.0 tag published every jar to Maven Central and attested all eight, and then the job went red. The linked-artifacts step got a 404, "no artifacts found", for a digest that does have an attestation — and because the step is fatal and runs before the release object is created, a release that had already succeeded ended with no GitHub Release and a failed run. The metadata is worth having. It is not worth that. The step is continue-on-error now, each record failure is a warning rather than an exit, and the run's log still names what was not written. The GitHub Release moves ahead of it, so the user-visible half of a release can never again be lost to the optional half. Two things the failure taught, both written where the next person will look. The endpoint is checked before writing, because it has no DELETE and a duplicate cannot be cleaned up. And GH_TOKEN falls back from an ARTIFACT_METADATA_TOKEN secret to GITHUB_TOKEN: whether the default token may write to this org-level endpoint is undocumented, the published docs state only artifact-metadata:read, and this tag is the first that tried. The 404 itself is not diagnosed here. It may be permission, it may be that a record needs an artifact the org already knows about rather than one it can reach through an attestation. Either way the release should not have failed while we find out. --- .github/workflows/release.yml | 60 +++++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 21 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fcb213f..5d9018e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -64,17 +64,47 @@ jobs: ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_KEY }} ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_KEY_PASSWORD }} + # The body is extracted from CHANGELOG.md, never written by hand: a release note composed + # separately is a second copy of what the changelog owns, and the two eventually disagree. + # Relative links resolve against the tag, so [STABILITY.md](STABILITY.md) points at the released + # revision rather than at whatever main says later. + - name: Create the GitHub Release + if: github.event_name == 'push' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + VERSION="${GITHUB_REF_NAME#v}" + awk -v head="## [$VERSION]" ' + index($0, head) == 1 { found = 1; next } + found && /^## \[/ { exit } + found { print } + ' CHANGELOG.md > notes.md + # An empty body means the tag and the changelog disagree. Fail rather than publish a blank one. + [ -s notes.md ] || { echo "::error::no CHANGELOG.md section for $VERSION"; exit 1; } + gh release create "$GITHUB_REF_NAME" --title "$GITHUB_REF_NAME" --notes-file notes.md + # Linked artifacts: tell GitHub which jars this build produced and where they now live. This is # metadata, not a second distribution channel — nobody resolves a dependency from here. It is # keyed on the same digest as the attestation above, so the repository can say what it built, # where it went, and that the two are provably the same file. # + # It runs last and cannot fail the job. On the v2.0.0 tag it returned 404 "no artifacts found" + # for a digest that does have an attestation, and it took down a release whose jars were already + # published and attested. A metadata step that can undo a successful publish is worth less than + # the metadata. `continue-on-error` and a per-artifact `|| true` are the fix; the run's log still + # says which records were not written. + # + # GH_TOKEN falls back to GITHUB_TOKEN. Whether that token may write to this org-level endpoint is + # undocumented — the docs state only `artifact-metadata:read` for reads — so a PAT with + # `artifact-metadata` goes in ARTIFACT_METADATA_TOKEN if the 404 turns out to be permission. + # # Only the main jars: sources and javadoc would triple the records for no added claim, and the # API documents no DELETE, so a record is permanent. The BOM has no jar and is skipped. - name: Record the published artifacts (linked artifacts) if: github.event_name == 'push' + continue-on-error: true env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ secrets.ARTIFACT_METADATA_TOKEN || secrets.GITHUB_TOKEN }} run: | VERSION="${GITHUB_REF_NAME#v}" # kdrant-core is multiplatform, so its JVM jar is published as kdrant-core-jvm and the @@ -84,8 +114,14 @@ jobs: kdrant-spring-boot-starter kdrant-spring-ai kdrant-langchain4j kdrant-micrometer; do module="${artifact%-jvm}" jar="$module/build/libs/$artifact-$VERSION.jar" - [ -f "$jar" ] || { echo "::error::$jar not found"; exit 1; } + [ -f "$jar" ] || { echo "::warning::$jar not found, not recorded"; continue; } digest="sha256:$(shasum -a 256 "$jar" | cut -d' ' -f1)" + # The API has no DELETE, so a duplicate cannot be cleaned up: check before writing. + if gh api "/orgs/${GITHUB_REPOSITORY_OWNER}/artifacts/$digest/metadata/storage-records" \ + --silent 2>/dev/null; then + echo "already recorded: $artifact $VERSION" + continue + fi echo "recording $artifact $VERSION $digest" gh api -X POST "/orgs/${GITHUB_REPOSITORY_OWNER}/artifacts/metadata/storage-record" \ -f name="$artifact" \ @@ -93,24 +129,6 @@ jobs: -f digest="$digest" \ -f registry_url="https://repo1.maven.org/maven2/" \ -f artifact_url="https://repo1.maven.org/maven2/io/github/nacode-studios/$artifact/$VERSION/$artifact-$VERSION.jar" \ - --silent + --silent || echo "::warning::could not record $artifact $VERSION" done - # The body is extracted from CHANGELOG.md, never written by hand: a release note composed - # separately is a second copy of what the changelog owns, and the two eventually disagree. - # Relative links resolve against the tag, so [STABILITY.md](STABILITY.md) points at the released - # revision rather than at whatever main says later. - - name: Create the GitHub Release - if: github.event_name == 'push' - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - VERSION="${GITHUB_REF_NAME#v}" - awk -v head="## [$VERSION]" ' - index($0, head) == 1 { found = 1; next } - found && /^## \[/ { exit } - found { print } - ' CHANGELOG.md > notes.md - # An empty body means the tag and the changelog disagree. Fail rather than publish a blank one. - [ -s notes.md ] || { echo "::error::no CHANGELOG.md section for $VERSION"; exit 1; } - gh release create "$GITHUB_REF_NAME" --title "$GITHUB_REF_NAME" --notes-file notes.md