If you've ever tried to release a macOS app, you've probably hit the code signing and Notarization wall.
You get through the entire Xcode build without a hitch, then the moment you try to create a distributable .dmg or .zip, you're suddenly buried in xcrun notarytool error messages that seem to change slightly every time — making it nearly impossible to find the root cause.
Antigravity is surprisingly good at untangling this mess. Paste an error log directly into the chat, and it'll tell you concretely: "this certificate configuration is wrong" or "this key is missing from your entitlements.plist." In this guide, I'll walk through automating the full flow — from code signing to Notarization — using Antigravity and GitHub Actions.
Why macOS Code Signing Feels Unnecessarily Complex
macOS distribution requires two main types of certificates.
Mac App Store (3rd Party Mac Developer Application): For apps distributed through the App Store, where Apple manages distribution.
Direct distribution (Developer ID Application): For apps distributed from your own website or GitHub. This path requires Notarization.
For indie developers, direct distribution is far more common. The workflow is: obtain a Developer ID Application certificate, sign the app, submit to Apple for Notarization, then staple the returned ticket back onto the app. Running this manually every release is tedious — and one wrong step means users see a "malicious software" dialog on launch.
From painful experience, the most common pitfall is "certificate is fine, but entitlements are incomplete." When you enable Hardened Runtime, many capabilities are blocked by default. Things like camera access, microphone, or network connections each need to be explicitly declared in entitlements.plist. Tell Antigravity what your app does, and it'll generate the entitlements file for you.
Step 1: Let Antigravity Audit Your Certificate Setup
Start by checking your environment. Run this in your terminal:
# List available Developer ID certificates for signing
security find-identity -v -p codesigning | grep "Developer ID"Paste the output into Antigravity's chat and ask it to verify that a valid Developer ID Application certificate exists. If it finds one, it'll generate the signing command right away.
If the certificate isn't showing up, import it like this:
# Import a .p12 downloaded from Apple Developer portal
security import ~/Downloads/DeveloperIDApplication.p12 \
-k ~/Library/Keychains/login.keychain-db \
-P "YOUR_P12_PASSWORD" \
-T /usr/bin/codesignAfter running this, ask Antigravity: "What does the -T flag do here, and how do I handle multiple certificates?" It's a great way to learn the fundamentals while making real progress.
Step 2: Generate Your Signing Script with Antigravity
Once the environment is ready, ask Antigravity to write the signing script. A prompt I use often looks like this:
Generate a shell script that signs a macOS .app bundle with my
Developer ID Application certificate and submits it for Notarization
all in one shot.
The app uses network connections and the camera.
The certificate SHA-1 hash is [your hash here].
The script Antigravity generates typically follows this structure:
#!/bin/bash
set -euo pipefail
APP_PATH="MyApp.app"
ENTITLEMENTS="entitlements.plist"
TEAM_ID="XXXXXXXXXX"
CERT_NAME="Developer ID Application: Your Name (XXXXXXXXXX)"
# Step 1: Sign frameworks and dylibs individually first
find "${APP_PATH}/Contents" \( -name "*.dylib" -o -name "*.framework" \) \
-exec codesign --force --sign "${CERT_NAME}" \
--options runtime --entitlements "${ENTITLEMENTS}" {} \;
# Step 2: Sign the main .app bundle
codesign --force --sign "${CERT_NAME}" \
--options runtime \
--entitlements "${ENTITLEMENTS}" \
"${APP_PATH}"
# Step 3: Verify signature
codesign --verify --deep --strict "${APP_PATH}"
spctl --assess --type execute "${APP_PATH}" && echo "✅ Signature verified"
# Step 4: Package and submit for Notarization
ditto -c -k --keepParent "${APP_PATH}" "${APP_PATH%.app}.zip"
xcrun notarytool submit "${APP_PATH%.app}.zip" \
--apple-id "${APPLE_ID}" \
--password "${APP_SPECIFIC_PASSWORD}" \
--team-id "${TEAM_ID}" \
--wait
# Step 5: Staple the Notarization ticket
xcrun stapler staple "${APP_PATH}"
echo "🎉 Notarization complete"Don't just copy and run this blindly. After it's generated, ask Antigravity: "I've heard the --deep flag is deprecated — is this script safe to use?" The answer is instructive: --deep is indeed discouraged by Apple, and signing frameworks individually (as shown above) is the more reliable approach. Antigravity stays current with these best practices.
Step 3: Generate an Accurate entitlements.plist
Incomplete entitlements are the number-one cause of Notarization failures. With Hardened Runtime enabled, you must declare every capability your app uses at runtime.
Example Antigravity prompt:
Generate an entitlements.plist for a macOS app that uses:
- Outbound network connections
- Camera
- Microphone
- Read/write access to the user's Documents folder
Hardened Runtime will be enabled.
The resulting entitlements.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<!-- Hardened Runtime: outbound network -->
<key>com.apple.security.network.client</key>
<true/>
<!-- Camera access -->
<key>com.apple.security.device.camera</key>
<true/>
<!-- Microphone access -->
<key>com.apple.security.device.microphone</key>
<true/>
<!-- User-selected file read/write -->
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
</dict>
</plist>Missing entitlements won't always fail Notarization — but the app will crash on the user's machine at runtime when it tries to use the blocked capability. Giving Antigravity a complete description of your app's features prevents these runtime surprises.
Step 4: Automate with GitHub Actions
Manually signing and notarizing before every release gets old fast. Here's how to wire it into GitHub Actions.
Ask Antigravity: "Convert the shell script above into a GitHub Actions workflow. Certificates will be stored in repository secrets."
The generated .github/workflows/release.yml:
name: macOS Release Build
on:
push:
tags:
- 'v*'
jobs:
release:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: Import certificate
env:
BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
run: |
echo "$BUILD_CERTIFICATE_BASE64" | base64 --decode > /tmp/cert.p12
security create-keychain -p "temp_password" build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p "temp_password" build.keychain
security import /tmp/cert.p12 \
-k build.keychain \
-P "$P12_PASSWORD" \
-T /usr/bin/codesign
security set-key-partition-list \
-S apple-tool:,apple: \
-s -k "temp_password" build.keychain
- name: Build, sign, and notarize
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
APP_SPECIFIC_PASSWORD: ${{ secrets.APP_SPECIFIC_PASSWORD }}
TEAM_ID: ${{ secrets.TEAM_ID }}
run: bash ./scripts/sign_and_notarize.sh
- name: Upload release artifact
uses: actions/upload-artifact@v4
with:
name: MyApp-release
path: MyApp.appAsk Antigravity "List every secret this workflow needs and how to obtain each one" and it'll walk you through generating BUILD_CERTIFICATE_BASE64 (base64 -i cert.p12 | tr -d '\n') and creating an APP_SPECIFIC_PASSWORD in Apple ID settings — no documentation hunting required.
For a deeper look at Xcode Cloud as an alternative CI approach, see Automating iOS CI/CD with Antigravity and Xcode Cloud.
Common Errors and How Antigravity Helps Diagnose Them
Error 1: The signature for binary ... is invalid
Usually caused by signing binaries in the wrong order with --deep. Paste the error into Antigravity and it'll identify which specific framework or binary needs to be signed independently first.
Error 2: The binary is not signed with a valid Developer ID certificate
The certificate is in your keychain but not being recognized — most often a missing security set-key-partition-list call in CI. Share the CI log with Antigravity and it'll pinpoint the missing step.
Error 3: Notarization returns invalid
Run xcrun notarytool log <submission-id> to fetch the full JSON log, then paste it into Antigravity. It'll parse the structured output and identify which binary is unsigned or which entitlement is misconfigured. In my experience, an unsigned third-party framework is the most common culprit.
Error 4: Gatekeeper warning persists after stapling
Run spctl -a -t open --context context:primary-signature -v MyApp.app to verify offline. Share the output with Antigravity in its terminal — it can immediately confirm whether the staple was applied correctly or whether a re-submit is needed.
Once you've automated macOS code signing and Notarization, you'll wonder why it ever felt so intimidating. With Antigravity in the loop — "paste the error, get a diagnosis, apply the fix" — the iteration cycle becomes fast enough that a failed Notarization is just a minor detour rather than a half-day investigation.
Start by running security find-identity -v -p codesigning and sharing the output with Antigravity. Once you know what you're working with, the rest of the automation falls into place quickly. For the full macOS menubar app development workflow that this automation completes, check out Building macOS Menu Bar Apps with Antigravity.