release-rapture-mac
Cut a notarized Rapture for Mac release end-to-end — run the build/sign/notarize/staple/DMG pipeline, then publish it (CHANGELOG cut, git tag at the build commit, GitHub Release with the DMG attached), and optionally install it locally. Use this whenever the user says "cut a release", "cut the relea
Install
mkdir -p .claude/skills/release-rapture-mac && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/13278" && unzip -o skill.zip -d .claude/skills/release-rapture-mac && rm skill.zipInstalls to .claude/skills/release-rapture-mac
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Cut a notarized Rapture for Mac release end-to-end — run the build/sign/notarize/staple/DMG pipeline, then publish it (CHANGELOG cut, git tag at the build commit, GitHub Release with the DMG attached), and optionally install it locally. Use this whenever the user says "cut a release", "cut the release", "release rapture-mac", "ship a release", "publish a release", "make a new release", or invokes `/release-rapture-mac`. This is the rapture-mac repo's release ritual; it encodes the non-obvious steps (version = commit count, tag points to the BUILD commit not the changelog-cut commit, push the tag before `gh release create`) that are easy to get wrong by hand.About this skill
Cut a Rapture for Mac release
Releases are notarized, Developer ID-signed DMGs attached to GitHub Releases (no Mac App Store). Scripts/release.sh does the mechanical half (build → sign → notarize → staple → DMG); the rest of this skill is the publish ritual around it. Human-readable maintainer docs for the same flow live in CONTRIBUTING.md → "Cutting a release" — keep the two in sync if you change one.
Preconditions (the script enforces these; check first)
- On
main, working tree clean,git pulldone. - One-time setup present:
Developer ID Applicationcert for teamP8PLTH44DF,notarytoolkeychain profilerapture-mac-notary, andcreate-dmg(brew install create-dmg). SeeCONTRIBUTING.md → "First-time release setup". - Sparkle signing is wired up (auto-update — required since v1.0.78).
Scripts/release.shStage 10 EdDSA-signs the DMG and appends theappcast.xmlentry; if it can't, the release ships with no auto-update path for that version. Verify both halves before building:which sign_updateresolves. The Sparkle CLI tools (sign_update,generate_keys) ship in the Sparkle release tarball, not the Swift package, and must live on a stablePATHdir — installing them only under/tmpis the trap (it's cleared on reboot, so a release that worked last week silently skips Stage 10). Install to~/.local/bin(already onPATH):cp /tmp/sparkle-tools/bin/{sign_update,generate_keys} ~/.local/bin/(or re-downloadSparkle-*.tar.xzfrom https://github.com/sparkle-project/Sparkle/releases if/tmpwas cleared).- The private EdDSA key is in the login keychain and matches the committed public key:
generate_keys -pmust print theSUPublicEDKeyfromScripts/set_sparkle_info.sh(aSyKYbbZsRRd12sg7D6m4j8HZcOCojVaIaKm2O5xqNo=). If releasing from another Mac, import the backed-up key first (generate_keys -f <file>). SeeCONTRIBUTING.md → "First-time release setup" item 4for backup/rotation.
- The
CHANGELOG.md[Unreleased]section already describes what's shipping. If it's empty or stale, stop and write it (with the user) before building — the release notes are sourced from it.
The version + tag rule — read this before tagging
- Version =
git rev-list --count HEADonmain→1.0.<count>(fromScripts/set_git_version.sh, baked into the build's Info.plist). - The build commit is HEAD before the changelog-cut commit. You build first (clean tree required), then make the cut commit — which increments the commit count by one. So the release tag must point at the build commit, not at HEAD after the cut. Capture the build SHA before cutting.
gh release create --target <short-sha>is rejected (target_commitish is invalid). Create and push the git tag explicitly first, thengh release createagainst the existing tag.
Steps
-
Capture the build commit and intended version.
BUILD_COMMIT=$(git rev-parse HEAD) VERSION=1.0.$(git rev-list --count HEAD) # sanity-check against what the build reports -
Run the pipeline in the background (build + notarization together take several minutes). Warn the user to stay at the keyboard: signing touches the keychain repeatedly — Stage 3b re-signs each Sparkle helper, then the framework + app, and Stage 10's
sign_updateuses the EdDSA key — so macOS may prompt to unlock the keychain (it asks for the login/Mac password) and/or to allowcodesign/sign_updateto use a key. The run blocks until each prompt is answered. Tell them to click Always Allow (not just Allow) and enter their password if asked, so later signing calls in the same run don't re-prompt. (Pre-unlocking withsecurity unlock-keychainbefore the run avoids the password prompt entirely.)./Scripts/release.sh 2>&1 | tee /tmp/rapture-release-$VERSION.logWhen it finishes (exit 0), read the Stage 11 summary for the authoritative
VersionandSHA-256. The pipeline notarizes twice (the.app, then the DMG) and, at Stage 10, EdDSA-signs the DMG + updatesappcast.xml— confirmstatus: Acceptedfor both notarizations, the staple/validate lines,spctl … accepted, and that Stage 10 reported updating the appcast (not a skip warning). The DMG is at/tmp/RaptureMacDerived/Rapture-<VERSION>.dmg. -
Cut the CHANGELOG. Turn
## [Unreleased]into a versioned section and leave a fresh empty[Unreleased]above it. Match the existing format exactly:## [Unreleased] ## [1.0.NN] - YYYY-MM-DD: <subtitle> Built from commit `<short BUILD_COMMIT>`. SHA-256: `<sha from summary>`. <one short paragraph of context> ### Added / Changed / Fixed / Tests … -
Bump the roadmap status line in
agent-os/product/roadmap.md(> Status: … latest public release v1.0.NN live on GitHub Releases (YYYY-MM-DD)and> Last Updated:). -
Commit the cut and push
main.release.shStage 10 already updatedappcast.xml(signed the DMG, appended the<item>) — include it so the Sparkle feed advertises the release:git add CHANGELOG.md agent-os/product/roadmap.md appcast.xml git commit -m "docs(changelog): cut v1.0.NN — <subtitle>" -m "<body w/ SHA-256>" \ -m "Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>" git push origin main(If Stage 10 warned that
sign_update/the EdDSA key was missing,appcast.xmlis unchanged — fix the setup per CONTRIBUTING → "First-time release setup" item 4, then re-run, before users can auto-update to this version.) -
Tag the build commit and push the tag (NOT HEAD — see the version+tag rule):
git tag v1.0.NN "$BUILD_COMMIT" git push origin v1.0.NN -
Create the GitHub Release from the pushed tag, DMG attached, notes from the changelog:
gh release create v1.0.NN \ --title "Rapture 1.0.NN — <subtitle>" \ --notes "<release notes incl. 'Built from commit … SHA-256: …'>" \ /tmp/RaptureMacDerived/Rapture-1.0.NN.dmgVerify:
gh release view v1.0.NN --json name,url,tagName,assetsshows the DMGuploaded, andgh release listshows it asLatest. -
(Optional) Install locally if the user wants this Mac on the new build — see "Install + make-it-the-only-version" below.
Install + make-it-the-only-version (optional)
pkill -f "Rapture.app/Contents/MacOS/Rapture" # quit running instances
MP=$(hdiutil attach -nobrowse -plist /tmp/RaptureMacDerived/Rapture-1.0.NN.dmg \
| plutil -extract system-entities.0.mount-point raw - | tail -1)
rm -rf /Applications/Rapture.app && ditto "$MP/Rapture.app" /Applications/Rapture.app
hdiutil detach "$MP"
spctl --assess --type execute --verbose=2 /Applications/Rapture.app # expect: accepted, Notarized Developer ID
open /Applications/Rapture.app
Expect a Full Disk Access re-grant + a restart prompt on first launch of the replaced app. Replacing the bundle can drop the previous build's FDA grant in TCC, so the new build re-runs onboarding asking you to enable Full Disk Access. After you enable it, the app prompts to quit and reopen — a running process doesn't pick up a newly-granted FDA permission until it relaunches, so this restart is required, not optional. Approve it (don't dismiss); capture won't work until you do. (A Sparkle auto-update preserves the FDA grant — confirmed by a 1.0.80→1.0.83 live-test, no re-prompt — so only a manual drag-replace like this triggers the re-grant + restart.)
To leave only the installed copy, remove stray build products under /tmp/RaptureMacDerived, /tmp/rapture-dd-*, and the Mac app's Xcode DerivedData/RaptureMac-* folder. Do not delete the …Debug-iphonesimulator/Rapture.app under DerivedData/Rapture-* — that's the separate iOS app (noisemeld.Rapture), a different product.
Gotchas (each has bitten a real release)
- Tag the build commit, not the cut commit. The cut commit's count is one higher than the released version. Tagging HEAD after the cut yields a tag whose rebuild would produce
1.0.(NN+1). gh release --target <sha>fails withtarget_commitish is invalidfor a short SHA. Push the tag first; create the release from the tag name.- Both the
.appand the DMG are stapled (the app is notarized + stapled before packaging), soxcrun stapler validate /Applications/Rapture.appsucceeds and offline first launch works.spctl --assessreturningaccepted / Notarized Developer IDremains the definitive check. (This is why the pipeline runs two notarization jobs.) - Build on the internal APFS volume (
/tmp/RaptureMacDerived). The repo's external SSD generates AppleDouble (._*) files that get copied into the bundle and breakcodesign.release.shalready routes derived data there. - Notarization can take up to ~10 min and
notarytoolexits 0 even onstatus: Invalid— always confirmstatus: Acceptedin the log, whichrelease.shalready parses. - Sparkle's nested helpers must be re-signed (Stage 3b), or the notary rejects the app. Sparkle ships
Updater.app,Autoupdate, and the Downloader/Installer XPC services ad-hoc-signed; Xcode embeds the framework without re-signing that nested code.codesign --verify --deep --strictpasses locally (it checks neither Developer ID validity nor secure timestamps), so the failure only appears at the notary as "not signed with a valid Developer ID certificate" / "signature does not include a secure timestamp" for paths underSparkle.framework.release.shStage 3b handles this; if you ever see those errors, that stage didn't run or didn't cover a new nested binary. This bit v1.0.79 (first Sparkle release). - Signing prompts for the keychain password and blocks. With Stage 3b there are several
codesigncalls plussign_update; macOS may ask to unlock the keychain (login password) or to allow key access, and the run halts until answer
Content truncated.