Skip to content

ci: fail the release when an asset is attached unsigned - #406

Merged
bernardladenthin merged 1 commit into
mainfrom
claude/release-asset-guards
Sep 1, 2026
Merged

ci: fail the release when an asset is attached unsigned#406
bernardladenthin merged 1 commit into
mainfrom
claude/release-asset-guards

Conversation

@bernardladenthin

Copy link
Copy Markdown
Owner

Summary

  • The attach jobs collect target/*.jar.asc with || true, so a signing step that produced nothing yields an attach that looks complete and is not: the jars land on the release without a signature and nothing says so.
  • The obvious fix — verify, refuse to attach — would defeat the reason both attach jobs deliberately run when their publish job failed: when Central is unreachable, the GitHub assets are the only way to get the build output at all. Withholding them over a missing signature is the worst outcome available.
  • So the check is split around the upload: report before it (never exits non-zero, one ::error:: per unsigned jar, count to $GITHUB_OUTPUT), upload unconditionally, assert after it. Assets always land; an unsigned release is loudly red instead of quietly wrong. -1 distinguishes "nothing was collected at all" from a signing failure.

Applied to github-snapshot and github-release-signed. The two steps are byte-identical with the copies now in srcmorph, BitcoinAddressFinder and streambuffer — verified by normalising the asset-directory name and hashing; sync any future edit to all four.

Test plan

  • publish.yml parses as YAML; the job graph is unchanged apart from the two added steps
  • The guard block is byte-identical across all four sibling repos (checked by hash, not by eye)
  • CI is green on this branch
  • Docs / CHANGELOG updated where applicable — rationale lives in workspace/policies/fat-jar-release-assets.md ("Attach first, then go red"), updated in the companion workspace PR

Nothing here can be exercised without a real publish_to_central dispatch, so the change is deliberately shaped so that its failure mode is "job red after the assets are attached" rather than "assets missing".

Related issues / PRs

Cross-repo rollout; companion PRs in srcmorph, BitcoinAddressFinder, streambuffer and workspace.

Checklist

  • I have read CONTRIBUTING.md and CODE_OF_CONDUCT.md
  • My commits follow Conventional Commits
  • No security-sensitive changes

Generated by Claude Code

The attach jobs collect `target/*.jar.asc` with `|| true`, so a signing step
that produced nothing yields an attach that looks complete and is not: the
jars land on the release without a signature and nothing says so.

The obvious fix -- verify, refuse to attach -- would defeat the reason both
attach jobs run on a failed publish job in the first place: when Central is
unreachable, the GitHub assets are the only way to get the build output at
all, so withholding them over a missing signature is the worst outcome
available.

So the check is split around the upload: report before it (never exits
non-zero, one annotation per unsigned jar, count written to the step
output), upload unconditionally, assert after it. Assets always land; an
unsigned release is loudly red instead of quietly wrong. -1 distinguishes
"nothing was collected at all" from a signing failure.

Byte-identical with the copies in the sibling repos; see
workspace/policies/fat-jar-release-assets.md.
@claude

claude Bot commented Sep 1, 2026

Copy link
Copy Markdown

Review Summary

Approach: ✅ The PR implements a sound safety check for unsigned release assets. The three-step pattern (report → upload → fail) ensures assets are never lost while making unsigned releases loudly visible.

Code Quality: ✅ The shell scripts are clean, readable, and follow best practices:

  • Uses set -uo pipefail for proper error handling
  • Clear distinction between "nothing collected" (-1) and "some unsigned" (count)
  • Appropriate use of GitHub Actions annotations and step outputs
  • Comments explain the philosophy well

Strengths

  1. Resilience philosophy - The design correctly prioritizes artifact availability over strict signature enforcement. If Central is unreachable, GitHub assets are the fallback path.

  2. Clear signaling - Using -1 to distinguish "nothing collected at all" from "N unsigned jars" is a thoughtful touch that helps operators understand what went wrong.

  3. Explicit ordering - Reports errors before upload (visible in logs), uploads unconditionally, then fails the job after. Assets can never be silently unsigned.

  4. Cross-repo consistency - The approach is identical across sibling repos, which aids maintainability.

Minor Observations

  1. Byte-identical duplication - The identical code block appears twice (snapshot + release jobs). This is noted as intentional for consistency with sibling repos. Monitor for future maintenance burden.

  2. Implicit assumption - The script checks for $jar.asc files assuming they're siblings of the JAR. This is correct per sign-fatjars.sh behavior, but the assumption is implicit rather than validated.

  3. Defensive coding - The find command with -maxdepth 1 correctly avoids subdirectories. Good defensive practice.

Verification

  • ✅ No security vulnerabilities identified
  • ✅ No shell injection risks
  • ✅ Step output handling is correct (GITHUB_OUTPUT)
  • ✅ The always() guard ensures final step runs even if earlier steps fail
  • ✅ YAML structure is valid
  • ✅ Logic correctly distinguishes failure modes (-1 vs count)

Recommendation: Ready to merge. The philosophy (attach first, fail after) is well-reasoned and the implementation is solid.

@claude

claude Bot commented Sep 1, 2026

Copy link
Copy Markdown

Inline Notes

Line: missing=-1 assignment when no jars found

This is well-designed: the -1 sentinel value clearly distinguishes "nothing was collected" (catastrophic failure) from "N unsigned jars" (signing failure). The output message correctly interprets both cases.

Line: if: ${{ always() && steps.signatures.outputs.missing != '0' }}

Correct use of always() to ensure the job fails even if preceding steps error. The != '0' comparison works for both string and numeric values. Good defensive step ordering.

Line: echo "missing=$missing" >> "$GITHUB_OUTPUT"

Proper GitHub Actions idiom. The variable will be available to downstream steps as steps.signatures.outputs.missing. ✅

@bernardladenthin
bernardladenthin deleted the claude/release-asset-guards branch September 1, 2026 06:58
@sonarqubecloud

sonarqubecloud Bot commented Sep 1, 2026

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants