Engineering · August 2026

Signed, notarized, and silently unauthorized

Our ShazamKit feature worked in every debug build and failed in every shipped one. The signature was fine. The staple was fine. The missing piece was a file most Mac developers have never had to think about.

The FeedsBar ticker in music mode, showing now-playing artwork and music-lane items

The feature at stake: the ticker re-tunes to whatever is playing, including music it hears in the room.

The setup

FeedsBar is a desktop news ticker for macOS, sold outside the App Store as a notarized Developer ID app. Version 1.2.0 added a listening mode: with permission, the app identifies music playing in the room via ShazamKit and re-tunes the ticker to that artist. In development it worked every time. Xcode, hit run, hum something, matched.

Then we cut the release build. Archive, export, notarize, staple, every check green. spctl accepted it. stapler validate passed. And every single ShazamKit match request came back as a 202 with an AMS 401 buried in the logs. Not one match, ever, from the exported app. Same source, same Mac, same microphone.

Why the shipped build was different

Mac developers learn early that entitlements ride in the code signature, and that for most capabilities that is the whole story. Sandbox, hardened runtime, microphone access: all asserted by the signature, all working in our exported build.

ShazamKit is not in that category. It is an App Service, granted to an App ID in the developer portal, and macOS honours the grant only when the app carries an embedded provisioning profile proving it. iOS developers never notice, because every iOS build embeds a profile. Direct-distribution Mac apps are the odd case: a Developer ID app needs no profile at all unless it uses one of these services, so the standard manual export produces a perfectly valid app with no profile inside, and the service quietly refuses it at runtime.

Debug builds worked because Xcode's run pipeline provisions automatically. The failure existed only in the artifact we actually shipped, which is the worst place for a failure to live.

The same app, two exportsManual signing, no profileAutomatic + -allowProvisioningUpdatesarchiveexport, signs cleannotarize + staple passNo embedded provisioning profileevery ShazamKit match: 202, AMS 401archive -allowProvisioningUpdatesdeveloper-id export, signingStyle automaticprofile minted and EMBEDDEDMac Team Direct Provisioning Profile insideroom audio identified on first try

The fix, in two flags

The release script now archives and exports with automatic signing, and passes -allowProvisioningUpdates in both places. That lets xcodebuild mint and embed the profile ("Mac Team Direct Provisioning Profile", expiry 2044) during the developer-id export:

xcodebuild -project "$PROJECT" -scheme "$SCHEME" -configuration Release \
  -archivePath "$ARCHIVE" -allowProvisioningUpdates \
  CODE_SIGN_STYLE=Automatic CODE_SIGN_IDENTITY="Apple Development" archive

# ExportOptions.plist
#   method:        developer-id
#   signingStyle:  automatic

xcodebuild -exportArchive -archivePath "$ARCHIVE" -allowProvisioningUpdates \
  -exportOptionsPlist ExportOptions.plist -exportPath "$EXPORT"

The archive signs with Apple Development; the export re-signs with the Developer ID certificate and embeds the profile. Verify it is actually in the product before trusting anything:

ls "FeedsBar.app/Contents/embedded.provisionprofile"
security cms -D -i "FeedsBar.app/Contents/embedded.provisionprofile" \
  | plutil -extract Name raw -o - -

One wrinkle worth keeping: our project file still pins Release to Manual signing with the Developer ID identity, because an Xcode holding only a development cert will otherwise "helpfully" rewrite the config. The script overrides at build time. Config files record intent; the pipeline enforces reality.

The decoy bug that cost the most time

While chasing this we spent hours convinced the app was not logging at all. log stream and log show printed nothing from our subsystem. The explanation is embarrassing in the way the best traps are: log is a zsh builtin. Inside a zsh-invoked script, log show runs the shell's builtin, not Apple's log tool, and either prints nothing or complains about arguments. The app had been logging correctly the whole time. Our scripts now say /usr/bin/log, always.

Takeaways

  • Signature valid, notarization passed, staple verified: none of these say anything about App Service authorization. That lives in the embedded profile.
  • If a capability works in debug and dies in the exported build, ask what Xcode's run pipeline gives you that your export does not. Provisioning is the usual answer.
  • Verify the exported artifact does the actual thing, on device, before shipping. Our release checklist now includes a live room-audio match from the stapled build.
  • In shell scripts on macOS, it is /usr/bin/log. The builtin will waste your afternoon.

FeedsBar is built by a very small team in Dublin. If you want to see the feature this post paid for, the home page shows it live, and the press kit has the rest.