Barkeep

Why macOS permissions vanish every time you rebuild

Updated 2026 · a 6-minute read · for developers

You're building a Mac app that needs Accessibility (or Screen Recording, or Input Monitoring). You grant it in System Settings, everything works, you make a one-line change, rebuild — and the app is silently denied again. The toggle still looks enabled. Toggling it off and on fixes it, until the next build.

This is one of the most disorienting parts of Mac development, because nothing reports an error. Your permission check just quietly returns false. Here's what's actually happening.

Advertisement

Ad space (responsive)

TCC identifies apps by signature, not by path

macOS stores privacy permissions in a database usually referred to as TCC (Transparency, Consent, and Control). When you flip a toggle in Privacy & Security, TCC records the grant against the app's identity.

Crucially, that identity is not "the app at this file path." It's the app's designated requirement — a rule derived from the code signature that describes what a binary must satisfy to count as this app. That design is deliberate and good: it means swapping a malicious binary into the same path doesn't inherit the original app's permissions.

The problem appears when your designated requirement isn't stable between builds.

Ad-hoc signing is the usual culprit

If you sign with codesign -s - (ad-hoc), there's no certificate involved. With no signing identity to anchor to, the designated requirement ends up derived from the code itself — effectively a hash of the binary.

Change one line of code, rebuild, and the hash changes. The new binary no longer satisfies the stored requirement, so TCC treats it as a different app and denies it. The old grant is still sitting in the database, matching a binary that no longer exists — which is exactly why the toggle still appears on while your app gets nothing.

You can see the requirement for yourself:

codesign -d -r- /Applications/YourApp.app

An ad-hoc build will show a requirement with a cdhash. That's the tell. A build signed with a certificate shows something referencing the certificate instead.

The fix: a stable local signing identity

You don't need a paid Apple Developer account to solve this. A self-signed certificate is enough, because what matters is that the certificate stays the same across builds, not that anyone trusts it.

Create a self-signed code signing certificate in your login keychain (Keychain Access can do this via Certificate Assistant, or you can script it with openssl and security import), then sign with it:

codesign --force --deep --sign "Your Local Signing" /path/to/YourApp.app

Now check the requirement again. You should see something along these lines:

designated => identifier "com.you.yourapp" and certificate leaf = H"<hash>"

That requirement depends on your bundle identifier and your certificate — neither of which changes when you edit code. Rebuild as often as you like; the grant survives.

A few gotchas worth knowing in advance

Advertisement

Ad space (responsive)

The other trap: duplicate copies of your app

Even with stable signing, there's a second failure mode that wastes hours, and it's worth checking before you go hunting for signature problems.

If two copies of your app exist — say a build output at build/YourApp.app and an installed copy in /Applications — macOS may register both in the Privacy list. They can look identical in the UI, showing the same name and icon. You grant one, but the copy you're actually running is the other, so your permission check keeps returning false while the toggle sits there enabled.

The signals to watch for:

The fix is to delete the stray copy, reset the permission for the bundle ID, then grant once to the single remaining app. It's worth making your build script remove its staging copy after installing, so the situation can't recur.

Debugging tip: your logs may not reach you

If your app is a background agent (LSUIElement), NSLog output doesn't reliably reach the unified log, which makes "is the permission actually active?" surprisingly hard to answer. A simple workaround is to write the result of your permission check into UserDefaults at launch along with a timestamp, then read it from the terminal:

defaults read com.you.yourapp

That gives you ground truth about what the running process sees, rather than what the Settings UI claims. The timestamp matters — it tells you whether you're reading a fresh result or a stale one from a previous build.

Summary

All of the above came out of building Barkeep, a free menu bar organizer for macOS. It's open source, so the signing script and the permission handling are there to read if you want a working reference.

View the source on GitHub

Related: Why menu bar apps ask for Accessibility · How to rearrange Mac menu bar icons