← Our FailuresAdhoc cdhash-only signing orphans TCC/entitlement grants on every rebuild and SIGKILLs sandboxed apps
advanced6 min read · updated 2026-06-20
Market & numbers — every figure sourced
tcc_regrants_per_rebuild1 permission prompt per protected resource per rebuildest: Observed behavior: each adhoc rebuild produces a new code identity, so every TCC-protected resource (mic, screen, camera) re-prompts once on next launch
voice_supervisor_restarts_per_day22 restarts/dayest: Internal Black Label voice supervisor log count on a day when adhoc rebuild orphaned the microphone TCC grant, causing a deaf-loop restart storm
Adhoc cdhash-only signing orphans TCC/entitlement grants on every rebuild and SIGKILLs sandboxed apps
This is one of the most expensive failure modes we hit shipping native macOS apps: a fix that was correct in source did nothing live, and an app that ran yesterday got instantly killed today. Both traced back to the same root cause: ad hoc code signing has no stable identity, and macOS uses that identity to remember who you are.
What we tried
We built five SwiftUI apps and a voice supervisor, signing every rebuild ad hoc (`codesign -s -`). Ad hoc signing uses no certificate — it only stamps the binary's code directory hash (cdhash) and identifies exactly one instance of that code "identifies exactly one instance of code". That was convenient: no Apple account, no provisioning, instant local builds. We assumed a rebuilt app was "the same app" as far as macOS was concerned.
It is not.
What broke
1. TCC grants orphaned on every rebuild. macOS tracks code identity using the code's designated requirement (DR). Ad hoc signed code carries no stable DR, so the system cannot tell that version N+1 is the same code as version N "macOS is unable to tell that version N+1 ... is the same code as version N". Every rebuild changed the cdhash, every cdhash looked like a brand-new app, and so every TCC-protected permission (microphone, screen recording, camera, accessibility) had to be re-granted. Our "hey ace" voice loop went deaf after a rebuild because the freshly-built binary no longer matched the microphone grant tied to the old cdhash. The supervisor saw silence, declared the loop dead, and restarted it — roughly 22 times in a day. The source code was perfect. The signature wasn't.
2. Entitlement grants and Keychain access scoped to the old identity also evaporated for the same reason — anything macOS keyed to "this exact app" was keyed to a cdhash we threw away on the next build.
3. Adhoc + app-sandbox = SIGKILL on launch. When we added `com.apple.security.app-sandbox` to an app and signed it ad hoc, the app was killed instantly on launch with `EXC_CRASH (SIGKILL (Code Signature Invalid))` and no usable crash report — the kernel rejected the code signature against the sandbox requirement before `main()` ran "Code Signature Invalid". Same story with a non-native `applesignin` entitlement on an adhoc build: immediate SIGKILL. The Apple "Sign in with Apple" button looked free; it was a landmine.
The fix
Three rules, all of which we now enforce:
- Reload by SIGTERM, never force-rebuild. To pick up a daemon/brain/loop change, send `SIGTERM` to the running pid and let the supervisor respawn the same on-disk binary (same cdhash → grants intact). Force-rebuilding to "redeploy" was the act that orphaned the grants. If the source on disk is already correct, do not regenerate the binary — just bounce the process.
- Gate Apple/OS features on the entitlements they require — at build time. Do not ship `app-sandbox` or `applesignin` on an adhoc build. Either remove the entitlement, or only enable the feature path (the Apple Sign-In button, the sandboxed file access) when the binary is signed with a real identity that carries the matching entitlement. We made the UI conditionally hide the Apple button unless the proper entitlement is present, so an adhoc dev build can't SIGKILL itself.
- For anything that must keep a permission across versions, sign with a stable Apple identity. Apple Development for dev, Apple Distribution for App Store, Developer ID for direct distribution — all carry a stable DR so version N+1 is recognized as the same app and the grant survives "Sign your code with a stable code-signing identity, ideally one issued by Apple". Ad hoc signing is fine for a throwaway CLI tool; it is wrong for any app that needs the mic, the screen, the sandbox, or a remembered login.
Apply it
- "My fix does nothing live" + a permission-dependent feature → check whether you rebuilt. If the cdhash changed, macOS forgot every grant. Reload the existing binary via SIGTERM instead.
- App built fine but dies instantly with no crash log → check `log show --predicate 'eventMessage CONTAINS "Code Signature"'` and your entitlements. An adhoc binary + `app-sandbox` (or any restricted/non-native entitlement) is a guaranteed SIGKILL.
- Before adding a slick OS feature (Sign in with Apple, sandboxed file picker, screen capture), ask: what entitlement does this need, and is my signing identity allowed to carry it? If not, the feature is a crash, not a feature.
- Treat the cdhash as your app's memory key. Anything you want macOS to remember about your app — permissions, entitlements, keychain items — is bound to that key. Throw the key away on every rebuild and the OS treats you as a stranger every time.