This runbook defines the full production path for shipping mac-copilot outside the Mac App Store using GitHub Releases + DMG, with optional in-app auto-updates.
Use this as the source of truth from prerequisites to final release testing.
Current workflow implementation lives at:
.github/workflows/release-dmg.yml
- Distribute signed + notarized
.dmgbuilds via GitHub Releases. - Keep releases tag-based (
vX.Y.Z) and reproducible. - Maintain a stable release channel and optional beta channel.
- Faster than App Store review cycles.
- Full control over release cadence and rollback.
- Works well for developer tools and companion desktop apps.
- Apple Developer Program membership.
- Developer ID Application certificate installed on a secure machine.
- Hardened Runtime enabled for release builds.
notarytoolprofile configured (App Store Connect API key or Apple ID method).
- GitHub repository with Releases enabled.
- Maintainers with permission to create tags/releases.
- Branch protection on
main(recommended).
- Xcode + command line tools.
xcrun notarytoolandxcrun stapleravailable.- Existing scripts already in repo:
scripts/build_dmg.shscripts/notarize_dmg.shscripts/release_dmg.sh
Add these in GitHub repository secrets:
MACOS_CERT_BASE64(Developer ID cert in.p12, base64-encoded)MACOS_CERT_PASSWORDKEYCHAIN_PASSWORD(temporary CI keychain)APPLE_NOTARY_KEY_IDAPPLE_NOTARY_ISSUER_IDAPPLE_NOTARY_PRIVATE_KEY_BASE64(base64 for.p8)SPARKLE_PUBLIC_ED_KEY(Sparkle EdDSA public key used by the app)- (Optional but recommended)
APPCAST_SIGNING_PRIVATE_KEY(Sparkle EdDSA private key for signing appcast entries)
- Use semantic versioning:
MAJOR.MINOR.PATCH. - Create releases only from tagged commits.
- Tag format:
vX.Y.Z(example:v1.4.2). - Never re-use a tag after publishing.
Recommended release policy:
PATCH: bug fixes only.MINOR: features, backward-compatible.MAJOR: breaking behavior or migration.
- On push of tags matching
v*. - Manual test trigger via
workflow_dispatch(supportsskip_notarize=truefor dry-run validation).
- Checkout + toolchain setup
- Import signing certificate to temporary keychain
- Install + build sidecar dependencies (
cd sidecar && npm ci && npm run build) - Build Release app
- Package DMG
- Notarize app and DMG (skipped in dry-run mode)
- Staple notarization ticket
- Verify codesign + stapling
- Generate SHA256 checksums
- Generate Sparkle appcast (if appcast signing key is configured)
- Publish appcast to
gh-pages(if appcast signing key is configured) - Create GitHub Release and upload assets
mac-copilot-<version>.dmgSHA256SUMS.txt- (Optional)
mac-copilot-<version>.zipfor alternative installers
- If notarization fails: fail workflow and do not publish release.
- If signing validation fails: fail workflow and do not publish release.
- Ensure
mainis green. - Confirm version/bundle metadata is updated in Xcode project.
- Verify sidecar runtime packaging is current.
- Verify sidecar lockfile/dependencies are committed and reproducible.
- Run targeted tests (at minimum critical integration tests).
- Confirm release notes draft includes user-facing changes and known issues.
Suggested command baseline:
xcodebuild -project mac-copilot.xcodeproj -scheme mac-copilot -destination 'platform=macOS' test- Merge release-ready PR into
main. - Create and push tag:
git tag vX.Y.Z
git push origin vX.Y.Z- Watch GitHub Actions run to completion.
- Validate generated release assets.
- Publish GitHub Release notes.
- Run CI dry-run (no notarization):
gh workflow run release-dmg.yml --ref main -f skip_notarize=true- Monitor run:
gh run list --workflow release-dmg.yml --limit 1
gh run watch- After dry-run succeeds, run normal tag release (
vX.Y.Z) to execute notarization + release publishing.
For non-App-Store macOS apps, recommended update path is Sparkle 2.
- Stable appcast feed for production users.
- Optional beta appcast for early adopters.
- Delta updates enabled where practical.
- Signature verification required for all updates.
- Host appcast + release artifacts on GitHub Releases.
- Serve appcast XML from GitHub Pages or your website.
- Current workflow publishes
appcast.xmltogh-pagesat:https://<org-or-user>.github.io/<repo>/appcast.xml
- Background update check (for example daily).
- Prompt user to install updates (or auto-download then prompt).
- Mandatory update messaging only for critical security fixes.
- Sign updates with Sparkle private key.
- Keep signing keys separate from developer machine credentials.
- Rotate/revoke keys if compromise is suspected.
Run this on a clean macOS user profile or clean machine.
- Download
.dmgfrom GitHub Release. - Verify checksum matches
SHA256SUMS.txt. - Install app from DMG to Applications.
- Launch succeeds without security warnings beyond normal first-open flow.
- App boots and sidecar starts/reuses successfully.
- Sign-in/auth flow works.
- Create project/chat, send first prompt, receive response.
- Delete chat/project flows behave correctly.
- Confirm bundled runtime works without system Node dependency.
- Confirm sidecar health endpoint and model list load correctly.
- App can reach appcast.
- Update prompt appears for newer test version.
- Download + install update succeeds.
- Updated app launches and retains expected user data.
If a bad release is published:
- Mark release as deprecated in notes.
- Keep previous stable release visible and recommended.
- Publish hotfix as next patch tag (
vX.Y.(Z+1))—do not overwrite old tag. - If Sparkle is enabled, repoint appcast to the known-good version.
- Rotate signing/notary credentials periodically.
- Review CI logs for notarization latency/failure patterns.
- Keep DMG/release scripts current as Apple tooling evolves.
- Review release checklist quarterly.
- Keep this runbook updated whenever release flow changes.
Symptom:
- GitHub workflow parser rejects
${{ runner.temp }}in job-levelenv.
Fix:
- Use
$RUNNER_TEMPinside step shell scripts instead of job-level expression references.
Symptom:
xcodebuild: ... project file format (77) ... future Xcode format.
Fix:
- Use a newer runner image (
macos-15) with newer Xcode.
Symptom:
- Build warns/fails because repo deployment target exceeds runner SDK support.
Fix:
- Override CI build target with
MACOSX_DEPLOYMENT_TARGET=15.5in the workflow build step.
As of February 27, 2026, the full stable-channel path is validated:
- Tag push (
vX.Y.Z) triggers.github/workflows/release-dmg.yml. - CI builds Release app, deep re-signs nested binaries/frameworks, notarizes DMG, staples ticket.
- CI publishes GitHub Release assets (
mac-copilot-vX.Y.Z.dmg,SHA256SUMS.txt). - CI generates
appcast.xmland publishes it togh-pages.
Live endpoints:
- Site root:
https://parham-dev.github.io/mac-copilot/ - Sparkle appcast feed:
https://parham-dev.github.io/mac-copilot/appcast.xml
Notes:
- Root site content can be empty; Sparkle only needs
appcast.xml. - Release workflow currently uses a single stable channel and manual in-app update checks.
Symptom:
main actor-isolated property/method ... in nonisolated contexterrors (exit 65).
Fix applied in source:
ShellNavigationHeaderStatemarked@MainActor.SignOutUseCase.execute()marked@MainActor.
Symptom:
- Build phase fails:
sidecar node_modules not found. Run 'cd sidecar && npm install'.
Fix:
- Add CI step to install/build sidecar before
xcodebuild.
Local DMG build:
./scripts/build_dmg.shNotarize existing artifacts:
./scripts/notarize_dmg.sh --keychain-profile "<profile>"End-to-end wrapper:
./scripts/release_dmg.sh --keychain-profile "<profile>"