ANTIGRAVITY LABJP
Articles/App Development
App Development/2026-05-05Intermediate

Automating macOS Code Signing and Notarization with Antigravity

macOS code signing and Notarization are unavoidable pain points in app distribution. Learn how to use Antigravity to generate scripts, diagnose errors, and automate the full pipeline with GitHub Actions.

antigravity435macoscodesignnotarization2github-actions9app-dev49

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/codesign

After 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.app

Ask 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.

Share

Thank You for Reading

Antigravity Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

App Dev2026-07-13
The Gate That Stops Visual Damage Before You Hand Bulk Image Optimization to an Agent
When an agent bulk re-encoded a few hundred wallpaper assets, a handful came back with dulled color. Size-reduction alone cannot catch that. Here is how to design a gate that stops bad conversions before merge using three axes — SSIM, ΔE, and file size — with a checker that runs on Pillow and scikit-image.
App Dev2026-07-09
Before an Agent's .proto Edit Silently Breaks Binary Compatibility: Gating Wire Compat with buf breaking
When an agent edits your .proto files, the text can look perfect while wire compatibility quietly breaks. Here is how to stop field-number reuse and unsafe type changes with a working gate built from buf breaking and reserved.
App Dev2026-07-03
Catching Download Size Regressions Before Submission Day — A Weekly Agent Gate for AAB/IPA Size Budgets
Mediation SDKs and bundled assets quietly inflate download size. A design for size ledgers, budget gates, and agent-driven delta attribution using bundletool and App Thinning reports.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →