From macos-distribution
This skill should be used when the user asks to "sign macOS app", "notarize app", "create a DMG", "codesign", "prepare for distribution", "Developer ID signing", "staple notarization ticket", "release a macOS app", "fix Gatekeeper rejection", or mentions Apple notarization, hardened runtime, Sparkle update signing, or distributing a macOS app outside the Mac App Store.
How this skill is triggered — by the user, by Claude, or both
Slash command
/macos-distribution:macos-distributionThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
End-to-end signing, notarization, stapling, and packaging for macOS apps distributed via Developer ID (outside the Mac App Store).
End-to-end signing, notarization, stapling, and packaging for macOS apps distributed via Developer ID (outside the Mac App Store).
Trigger this skill for:
.app bundle with a Developer ID Application certificatenotarytoolspctl Gatekeeper rejections, stapler failures, or notary log errorsOut of scope: Mac App Store submission (different certificate, no notarization step) and iOS/tvOS/watchOS distribution.
Release build → sign nested code (inside-out) → sign outer .app
→ notarize → staple → verify
→ package DMG/ZIP → sign+notarize+staple container
Apple enforces inside-out signing: nested binaries must be signed before their containers. The bundled scripts/sign_and_notarize.sh automates the full traversal.
Before signing, confirm:
security find-identity -v -p codesigning)ENABLE_HARDENED_RUNTIME=YESInfo.plist has CFBundleIdentifier, CFBundleVersion, CFBundleShortVersionString, and LSMinimumSystemVersionRun scripts/preflight.sh /path/to/MyApp.app to validate all five.
Apple ID with two-factor auth requires an app-specific password (appleid.apple.com → Sign-In and Security → App-Specific Passwords).
Store credentials in keychain once:
xcrun notarytool store-credentials NOTARYTOOL_PROFILE \
--apple-id "[email protected]" \
--team-id "ABCDE12345" \
--password "xxxx-xxxx-xxxx-xxxx"
After this, all notarytool calls use --keychain-profile NOTARYTOOL_PROFILE and never see the password again. Use scripts/setup_credentials.sh for an interactive walkthrough.
Apple's signature is a Merkle tree: the outer signature only covers code that was already signed at the time. Sign in this order:
*.dylib and *.framework/Versions/*/Frameworks/* (deepest first)Contents/Frameworks/*.frameworkContents/XPCServices/*.xpcContents/Helpers/* and Contents/Library/LoginItems/*Contents/MacOS/.app (Step 2)For each nested item:
codesign --force --options runtime --timestamp \
--sign "Developer ID Application: <Name> (<TEAMID>)" \
<path-to-nested-item>
Never use --deep. It applies the outer entitlements to nested code (wrong) and is officially deprecated. The provided sign_and_notarize.sh walks the bundle bottom-up automatically.
After all nested code is signed:
codesign --force --options runtime --timestamp \
--entitlements assets/entitlements.plist.template \
--sign "Developer ID Application: <Name> (<TEAMID>)" \
Build/Release/MyApp.app
--options runtime enables hardened runtime — without it notarization rejects.
Notarytool requires a ZIP, DMG, or PKG (it does not accept loose .app):
ditto -c -k --keepParent MyApp.app MyApp.zip
xcrun notarytool submit MyApp.zip \
--keychain-profile NOTARYTOOL_PROFILE --wait
--wait blocks until Apple responds (typically 30 s – 2 min). On Invalid status, fetch the diagnostic log:
xcrun notarytool log <submission-id> --keychain-profile NOTARYTOOL_PROFILE
Common causes are listed in references/troubleshooting.md.
Embed the notarization ticket so Gatekeeper can validate offline:
xcrun stapler staple MyApp.app
If stapler fails with "cannot find the ticket," wait 1–2 minutes for Apple's CDN to propagate, then retry.
Run all four checks (also bundled in scripts/verify_signed.sh):
codesign -dv --verbose=4 MyApp.app # signature metadata
codesign --verify --strict MyApp.app # signature validity
spctl -a -vvv -t exec MyApp.app # Gatekeeper acceptance
stapler validate MyApp.app # ticket embedded
Expected output: all four succeed with no errors. spctl should print source=Notarized Developer ID.
Apple requires the container itself to be notarized when distributed. The bundled script handles this:
scripts/create_dmg.sh MyApp.app MyApp-1.0.dmg
Steps performed:
hdiutil create -srcfolder ... -format UDZOnotarytool and waitThe signed+stapled DMG can be distributed as-is.
ZIP archives cannot be stapled — staple the app first, then zip:
xcrun stapler staple MyApp.app
ditto -c -k --keepParent MyApp.app MyApp-1.0.zip
The stapled ticket inside the app travels with the ZIP.
For apps using Sparkle 2.x for auto-updates:
generate_keys tool (stored in keychain)Info.plist as SUPublicEDKeysign_update MyApp-1.0.zip → produces an ed25519 signature for the appcast XMLSparkle's embedded XPC services (org.sparkle-project.*) must themselves be signed and notarized as part of Step 1. See references/sparkle.md for appcast format and XPC entitlement requirements.
| Symptom | Likely cause | Fix |
|---|---|---|
| Notary log: "hardened runtime not enabled" | Missing --options runtime or built without ENABLE_HARDENED_RUNTIME | Re-sign with --options runtime |
| Notary log: "binary uses an SDK older than ..." | Built with old Xcode | Build with current Xcode |
spctl rejects after notarization | App not stapled, or modified after stapling | Staple, do not modify after |
| Stapler: "cannot find the ticket" | CDN propagation delay | Wait 1–2 min, retry |
codesign rejects nested framework | Already had ad-hoc signature from build | Strip with codesign --remove-signature, re-sign |
| Notary log: "missing secure timestamp" | --timestamp flag omitted | Re-sign with --timestamp |
For detailed diagnosis see references/troubleshooting.md.
scripts/)preflight.sh — Validate prerequisites before signingsetup_credentials.sh — Interactive notarytool store-credentials walkthroughsign_and_notarize.sh — End-to-end inside-out signing + notarize + staple for an .app bundlecreate_dmg.sh — Build, sign, notarize, staple a DMG containerverify_signed.sh — Run the four-check verification suitereferences/entitlements.md — Hardened runtime exception entitlements (JIT, library validation, debugger, etc.)references/troubleshooting.md — Notary log error catalog and recovery proceduresreferences/sparkle.md — Sparkle 2.x EdDSA appcast signing and XPC entitlementsassets/entitlements.plist.template — Minimal hardened runtime entitlements starting pointProvides behavioral guidelines to reduce common LLM coding mistakes, focusing on simplicity, surgical changes, assumption surfacing, and verifiable success criteria.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
npx claudepluginhub kjetilge/kjetil-claude-marketplace --plugin macos-distribution