Skip to content

Instantly share code, notes, and snippets.

@Sidelobe
Last active December 18, 2025 08:34
Show Gist options
  • Select an option

  • Save Sidelobe/d0ef75925541ae7422e5c7388e6ecd99 to your computer and use it in GitHub Desktop.

Select an option

Save Sidelobe/d0ef75925541ae7422e5c7388e6ecd99 to your computer and use it in GitHub Desktop.
Notarization of a macos App Created With PyInstaller

Why

This gist contains my personal notes and gotchas identified while trying to create an .app out of a Python script and distribute it directly (outside App Store) via a .dmg. This subject has been the source of a lot of confusion and frustration throughout the years. Since altool was retired and replaced by notarytool things have improved in terms of documentation IMHO, but at the same time the 'screws are tightened' by Apple's Gatekeeper in incresingly short intervals. While I welcome added security as a whole, us developers need to jump through quite a few more hoops. It's almost as if they want to encourage you to distribute via the App Store...

Code Signing

All executable (incl. libaries) needs to be signed. pyinstaller does this with the codesign_identity specified (via command line option or in .spec file) and recursively to all dependencies bundled, including the Python executable.

Signing the .dmg

The .dmg needs to be code-signed, too: codesign --sign ${team_id} "${app_name}.dmg".

When using create-dmg, one can use --codesign (and --notarize) options. I prefer to do this manually afterwards, since create-dmg (to my knowledge) only supports the

Checking Signing Status

codesign --display --deep MyApp.app (add -v or -vv, -vvv for more details). Never use --deep for actual signing (deprecated and 'bad idea' according to Apple Dev)

Notarization

When notarizing via notarytool, the .app (needs to be zipped or similar) or .dmg gets uploaded to Apple's servers and officially notarized/validated for distribution.

First off: there's no need to notarize the .dmg and the .app; notarizing the former notarizes all contained .app as well.

There are 2 ways of authenticating: directly with AppleID and an 'application-specific password' (not your apple ID password!) or via a dedicated keychain item (cf. Docu from Apple).

Notarizing is simple enough:

xcrun notarytool submit --wait --team-id <TEAMID> --apple-id <NOTARIZEUSER> --password <NOTARIZEPASS> "MyApp.dmg"

If you're running any commands after the notarization (e.g. copying the notarized .dmg to the Pipeline Artifacts folder!) --wait, since the notarization can take a couple of seconds (30 sometimes).

A lot of more in-depth info here: Notarisation Resources Meta-Thread on Apple Dev Forum.

Checking Notarization Status (Pre-Distribution Checks)

The best and most modern method is:. syspolicy_check distribution MyApp.app, which will return either:

  • "App passed all pre-distribution checks and is ready for distribution."
  • "App has failed one or more pre-distribution checks."

other methods:

  • spctl -vvv --assess --type exec MyApp.app
  • spctl -a -vvv -t install MyApp.app

Getting the Notarization Log from the Server

When running the notarization command, you will see an output like this:

Conducting pre-submission checks for MyApp.dmg and initiating connection to the Apple notary service...
Submission ID received
  id: 1886a05f-47ed-463e-a06f-aa55624a6fa0
Upload progress: 100.00% (14.2 MB of 14.2 MB)   
Successfully uploaded file
  id: 1886a05f-47ed-463e-a06f-aa55624a6fa0

To query the server for the log, use this command (=1886a05f-47ed-463e-a06f-aa55624a6fa0 in the above example):

xcrun notarytool log <RequestUUID> --team-id <TeamId> --apple-id <AppledId>

This will prompt you to enter the 'application-specific password'. The output/response will be a JSON string that should show you which file(s) caused notarization to fail and why.

Stapling and 'Error 65'

'Stapling' is the process of attaching the "receipt" of the Notarization process to the distributable (cf. The Pros and Cons of Stapling on Apple Dev).

I became aware of this because I was getting this error:

App has failed one or more pre-distribution checks.
---------------------------------------------------------------
Notary Ticket Missing
    File: <MyApp.dmg>
    Severity: Fatal 
    Full Error: A Notarization ticket is not stapled to this application. 
    Type: Distribution Error 

Stapling is simple enough: xcrun stapler staple 'MyApp.dmg' ... unless

.. you get an 'Error 65' when trying to staple (cf. Thread on Apple Dev):

CloudKit query for MyApp.dmg (2/e1d0b6902e7460eda2f5bcc8162dde20c41de95b) failed due to "Record not found".
Could not find base64 encoded ticket in response for 2/e1d0b6902e7460eda2f5bcc8162dde20c41de95b
The staple and validate action failed! Error 65.'

In my (limited) experience, this can have 2 reasons:

  • You modified/re-signed your file between notarization and stapling
  • You forgot --wait when notarizing. Stapling cannot happen while notarization is ongoing (usually takes seconds).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment