Skip to content

Verify the artifact handoff on every push, not just at release - #22

Merged
wouterdebie merged 1 commit into
mainfrom
ci-artifact-roundtrip
Sep 17, 2026
Merged

wouterdebie merged 1 commit into
mainfrom
ci-artifact-roundtrip

Conversation

@wouterdebie

Copy link
Copy Markdown
Owner

release.yml was the only workflow here, so the build -> release artifact handoff only ran when a tag was cut. A regression in it surfaces mid-release, after signing and notarization have already spent their time on a bundle that was never going to work.

This adds a ci.yml that builds the bundle ad-hoc and pushes it through the same upload-artifact/download-artifact pair, then checks what actually breaks.

Why test -L and not test -e. upload-artifact flattens symlinks, and Sparkle.framework is a versioned bundle held together by them. A flattened framework is a real directory tree. Verified against a deliberately flattened copy:

test -e Versions/Current              PASSES (would not catch it)
test -d Versions/Current              PASSES (would not catch it)
test -x Versions/Current/Autoupdate   PASSES (would not catch it)
test -L Versions/Current              FAILS  -> caught
codesign --verify --deep --strict     "unsealed contents present in the
                                       root directory of an embedded framework"

Two independent detections, which is what you want for a failure whose other symptom is a notarized app that won't launch.

It also asserts the @executable_path/../Frameworks rpath. Package.swift adds that by hand because SwiftPM doesn't, and without it the app builds, signs, notarizes and passes every codesign check before dying at launch on @rpath/Sparkle.framework. That shipped once already.

No credentials in this workflow at all, on the same reasoning that split release.yml into two jobs.

Round-trip technique from the Don't Miss CI, which landed the same check in a4087bb.

🤖 Generated with Claude Code

release.yml was the only workflow, so the build -> release artifact
handoff was exercised only when a tag was cut. A regression there
surfaces mid-release, after signing and notarization have already run
against a bundle that was never going to work.

CI now builds the bundle ad-hoc and pushes it through the same
upload/download-artifact pair release.yml uses, then asserts the
framework survived. The assertions are `test -L`, not `test -e`: a
flattened symlink is a real directory that passes every existence
check and fails only later, at codesign or at launch. Verified against
a deliberately flattened bundle -- `-e`, `-d` and `-x Autoupdate` all
pass on it; only `-L` catches it.

Also asserts the rpath Package.swift adds by hand, since SwiftPM does
not add it and the app builds, signs and notarizes without it before
dying at launch on @rpath/Sparkle.framework. That one shipped once.

Technique from the Don't Miss CI, which added the same round-trip check.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@wouterdebie
wouterdebie merged commit eed931b into main Sep 17, 2026
1 check passed
@wouterdebie
wouterdebie deleted the ci-artifact-roundtrip branch September 17, 2026 18:18
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.

1 participant