Skip to content

0012. Release: the macOS package is signed off CI

Date: 2026-09-28

The macOS package that ships is built and Developer ID signed on a developer's machine; the CI package is never attached to a release. Extracted verbatim from the "Tagged Releases" section of the root README, whose release material now lives in Releasing Tag Firmware and Host Tools; the procedure is in Releasing the host tools.

Context, decision and alternatives

macOS CI builds are signed ad-hoc (-DMACOS_CODE_SIGN_IDENTITY=-) rather than with the Developer ID identity, which is not available to the runner. Signing is not optional here. With MACOS_SIGN_APPS=OFF nothing runs codesign on the bundle, so the only signature present is the ad-hoc one the linker applies to arm64 Mach-O files:

CodeDirectory v=20400 flags=0x20002(adhoc,linker-signed)
Sealed Resources=none
qtmonitor.app: code has no resources but signature indicates they must be present

That signature seals the executable but writes no Contents/_CodeSignature/CodeResources, which an app bundle requires, so the bundle fails codesign --verify and arm64 macOS refuses to launch it. Users see "the application is damaged and can't be opened" rather than the usual unidentified-developer prompt.

That is why the CI macOS package is not attached to releases. It is a build check and a workflow artifact, nothing more. release.yml opens the release as a draft with only the Windows ZIP attached; the shippable macOS DMG is built and signed on a machine holding the Developer ID certificate by host/tools/release-macos.sh, uploaded to that draft by hand, and the release is published once it is there.

Signing in CI instead would mean storing the Developer ID certificate and its password as repository secrets and importing them into a temporary keychain before the package step. In a public repository that makes the private key recoverable by anyone who can run a workflow, so it is deliberately not done. install_macos_codesign in cmake/DeployQt.cmake already selects the hardened runtime (--options runtime) and a secure timestamp for a real identity and omits them for -, so the local build needs no special handling.