Repository navigation
Build the macOS release archive on the macOS runner, where its permissions still exist - #70
Merged
Merged
Conversation
Trigger: the published v0.2.1 macOS asset - 0 of 344 entries carry an execute bit, and extracting it and running Contents/MacOS/matteloop gives permission denied; test test_release_validation_requires_an_executable_macos_launcher No macOS artifact this project has published can start. The bundle was uploaded unpackaged with actions/upload-artifact, which does not preserve file modes, and the ZIP was then assembled from those 0644 files on a Linux runner. The frozen smoke test runs earlier, on the macOS runner, against a bundle that is still intact - which is why four releases went out green while none of them could launch. The archive is now created on the macOS runner with ditto, uploaded as a finished file, and only moved into place by publish. Nothing reconstructs a macOS bundle on another platform any more; the Windows path is unchanged, since it has no permission bits to lose. --norsrc matters. Files created on a macos-15 runner carry a com.apple.provenance extended attribute, and ditto preserves xattrs by default, so --sequesterRsrc would write an AppleDouble tree and give the archive a second top-level entry beside MatteLoop.app. With --norsrc the listing matches what v0.2.1 shipped, modes and symlinks intact. One file legitimately changes shape: libonnxruntime.1.dylib travelled as a second 33 MB copy because upload-artifact follows symlinks, and is now stored as the symlink build.py creates - the same one the runner's smoke test exercised. A check on the published archive guards it: publish extracts the ZIP, reads CFBundleExecutable from Info.plist and fails the job if that file is not executable. It runs before the release is created and on the artifact a user would download, not on the build directory - the distinction that let this ship four times. Tests cover the guard on an executable and a non-executable launcher, that the macOS archive reaches assets/ byte-identical with its four source and checksum sidecars, and the step order the guard depends on. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Every published macOS build is unlaunchable. The bundle was uploaded unpackaged
with
actions/upload-artifact, which does not preserve file modes, and the ZIPwas then assembled from those
0644files on a Linux runner. The frozen smoketest runs earlier, on the macOS runner, against a bundle that is still intact —
so four releases went out green while none of them could start.
What changed
The archive is created on the macOS runner with
dittoand uploaded as afinished file;
publishonly moves it intoassets/. Nothing reconstructs amacOS bundle on another platform any more. The Windows path is untouched — it
has no permission bits to lose.
--norsrcis load-bearing. Files created on amacos-15runner carry acom.apple.provenancexattr anddittopreserves xattrs, so--sequesterRsrcwould write an AppleDouble tree and give the archive a second top-level entry
beside
MatteLoop.app. With--norsrcthe listing matches what v0.2.1 shipped,modes
100755and framework symlinks intact.One file legitimately changes shape:
libonnxruntime.1.dylibshipped as asecond 33 MB copy because
upload-artifactfollows symlinks, and is now storedas the symlink
scripts/build.pycreates — the same one the runner's smoke testexercised. Same paths, smaller archive.
The guard
Before
gh release create,publishextracts the ZIP, readsCFBundleExecutablefromInfo.plistand fails the job if that file is notexecutable. It runs on the artifact a user would download, not on the build
directory — the distinction that let this ship four times.
Tests
All four fail against the workflow on
mainand pass here:test_release_validation_requires_an_executable_macos_launcher— the guarditself, on a
0o755and a0o644launcher.test_release_packaging_preserves_macos_archive_and_source_sidecars— thearchive reaches
assets/byte-identical, with all four source and checksumsidecars beside it.
test_release_workflow_builds_on_dispatch_tags_and_media_stack_changes— thestep order and platform conditions the guard depends on.
tests/releaseis green (28 passed).Not in scope
The published releases stay as they are, as decided in the issue. A user who
already has one repairs it with
chmod +x MatteLoop.app/Contents/MacOS/matteloop,which is verified sufficient — the full runtime smoke test passes afterwards.
Closes #57
🤖 Generated with Claude Code