Files
nucleic/scripts/macos-notification-fix

Removing the boot-time notification banners from the macOS golden base

Every clone boots with three persistent alert-style banners in the top-right, over the screenshotted desktop:

Banner Real source Notes
Data Access Blocked — "NucleicVMAgent tried to access your data from other apps and was blocked" NucleicVMAgent lacks Full Disk Access; TCC blocks it and posts this The default computer-use path is host-side, so this is cosmetic unless you use the in-guest native/ax path — but the block fires every time the agent runs.
Multiple Extensions Added — "Xcode-beta added multiple extensions" Background Task Management (com.apple.BTMNotificationAgent) BTM already records Xcode-beta as notified, so it will not re-post — see "replay" below.
Tips — "Learn how to take a screenshot" Tips (com.apple.tips / tipsd) tipsd runs and the Tips window auto-launches despite the Phase 5¾ launchctl disable.

Two things must be true — and the constraints are tight

This was verified on a live macOS 27 clone of the current base:

  • SIP is ENABLED in the sealed base, and it is not MDM-enrolled (no configuration profiles). That rules out three whole families of fix:
    • TCC.db cannot be read or written from any shell (not even root) → no scripted Full Disk Access grant, no scripted per-app notification toggle.
    • PPPC (com.apple.TCC…) and com.apple.notificationsettings profiles are only honored via MDM — a manually-installed .mobileconfig is ignored.
    • The DND assertion store and the delivered-notification store are TCC-protected from the shell.
  • The banners are REPLAYED from the base snapshot, not regenerated each boot. BTM already marks the extensions notified (it won't post again), yet the banner appears on every clone — so the base was sealed with these alerts still on screen (undismissed), and every clone inherits that screen state. killall NotificationCenter/usernoted do not clear persistent alerts (the UI just relaunches and re-draws them), which is why the existing timer fails.

Do NOT use a blanket suppressor (DND / notifications-off)

A tester may be exercising an app's own notifications, so the fix must be surgical — kill only these three sources and leave the notification framework fully working. That rules out Do Not Disturb and any global "notifications off": DND is verified to suppress new notifications, so it would also swallow the banners a developer is trying to see.

It also means the existing killall NotificationCenter LaunchAgent (Phase 5⅞+) must be removed — beyond being ineffective against these persistent alerts, its 60-second sweep would dismiss an app-under-test's intended notifications. It is actively wrong for the notification-testing use case.

The fix: surgical, at base-build time, captured in the snapshot

Because SIP blocks scripting the relevant stores, the durable fix is a small set of targeted settings applied once while building the golden base — each scoped to a single source, each captured in the sealed snapshot, none touching the notification framework as a whole:

  1. NucleicVMAgent → Full Disk Access. Removes Data Access Blocked at the source and unblocks the agent's native path. Only affects the agent.
  2. Tips → notifications off (and actually stop tipsd). Removes Tips. Per-app; every other app still notifies normally.
  3. Seal the base clean. Dismiss any remaining on-screen alert before the final shutdown (Notification Center ▸ Clear All). BTM won't re-post the extensions banner once it's dismissed, so the clones stay clean.
  4. Remove the killall NotificationCenter LaunchAgent (Phase 5⅞+).

How you apply 12 depends on how you build the base — pick one lane:

Lane A — build with SIP off (matches the script's existing TCC path)

The provisioner already writes the agent's other TCC grants via sqlite3 when SIP is disabled (Phase 7c/7d). In that same window:

  • FDA: add kTCCServiceSystemPolicyAllFiles for the agent bundle to the Phase 7d grant loop — one extra service, keyed by xyz.blakeslee.nucleic.vmagent with the app's designated requirement.
  • Tips: the per-app notification flag lives in ~/Library/Preferences/com.apple.ncprefs.plist (user domain, not SIP-protected) — settable while building, but the format is opaque; the reliable path is the GUI toggle in Lane B, done once and snapshotted.

Lane B — SIP stays on (this base today): GUI toggles during the build

The base build already drives a GUI session, and GUI settings persist into the snapshot. Do these once, before sealing:

  • FDA: System Settings ▸ Privacy & Security ▸ Full Disk Access ▸ add NucleicVMAgent.
  • Tips: System Settings ▸ Notifications ▸ Tips ▸ turn Allow Notifications off.
  • Then Notification Center ▸ Clear All, and shut down to seal.

Lane C — adopt MDM

If the base is ever MDM-enrolled, the two profiles in this directory are the clean declarative form of 12 (they are per-app / agent-scoped, i.e. not a blanket suppressor):

  • nucleic-vmagent-fda.mobileconfig — Full Disk Access for the agent.
  • nucleic-suppress-notifications.mobileconfig — disables notifications for com.apple.tips and com.apple.BTMNotificationAgent only.

Both validate with plutil -lint. They are inert without MDM.

Summary

  • Root-cause each source (FDA for the agent; Tips off); do not blanket-suppress.
  • Seal the base with the alerts dismissed — that is what actually stops the per-boot replay.
  • Delete the killall NotificationCenter timer; it is ineffective here and harmful to the notification-testing use case.
  • Lane A (SIP-off build) makes FDA scriptable; Lane B (GUI, SIP-on) works on the current base; Lane C (MDM) uses the profiles here.