Update it, diagnose why it won't load, and remove it cleanly. The troubleshooting table is the part you'll come back to.
You don't "reinstall." You bump CFBundleVersion, rebuild, and submit another activation request for the same bundle ID. The OS notices a version it already has and calls your delegate:
During development the running provider is torn down (stopFilter with reason .noNetworkAvailable/.userInitiated) and the new one started. No reboot — the user-space store just swaps the bundle.
Three levels, least to most aggressive:
A content filter has both a system extension (remove via deactivation / System Settings ▸ Login Items & Extensions) and a filter configuration (remove via System Settings ▸ Network ▸ Filters, or NEFilterManager.removeFromPreferences). Pull both or you'll leave a half-configured filter behind.
| Symptom | Cause | Fix |
|---|---|---|
| Killed: 9 (exit 137) | Restricted entitlement not authorized by a provisioning profile (AMFI kill). | Embed a matching embedded.provisionprofile; ensure entitlements ⊆ profile. (Lesson 5 §2.) |
| code=2 | OSSystemExtensionErrorDomain: app missing com.apple.developer.system-extension.install. | Add that entitlement to app.entitlements, re-sign. |
| code=1 | "unknown/unsupported" — bundle ID mismatch, ext not embedded at the right path, or Info.plist malformed. | Verify the .systemextension is under Contents/Library/SystemExtensions/ and its CFBundleIdentifier matches the request. |
| code=8 | "codeSignatureInvalid" — entitlements don't match the profile, or signing chain broken. | Re-create the profile with the exact capability; sign inside-out; check codesign -vvv --deep. |
| waiting for user | Activation succeeded but no one approved it. | System Settings ▸ Login Items & Extensions ▸ Network Extensions ▸ toggle on. |
| cannot allow outside /Applications | SIP on requires the host app in /Applications. | cp -R the app into /Applications (or use developer mode with SIP off). |
| developer blocked | systemextensionsctl developer on refused — SIP is enabled. | Use the provisioning path, or disable SIP from 1TR first. |
| handleNewFlow never fires | Extension activated but content filtering not enabled, or rules don't route to the provider. | Confirm NEFilterManager.isEnabled = true saved (2nd approval); use defaultAction: .filterData. |
systemextensionsctl list tells you the state; sysextd logs tell you why a transition failed; your os_log subsystem tells you what the provider is doing. Check them in that order.
Bump CFBundleVersion, rebuild, submit another activation request; implement actionForReplacingExtension → .replace. No reboot.
The system extension (deactivation / Login Items & Extensions) and the filter configuration (Network ▸ Filters / removeFromPreferences).
code=8 on activation. First suspect?Code-signature/entitlement vs. provisioning-profile mismatch. Regenerate the profile with the right capability and re-sign.
log show/stream --predicate 'process == "sysextd"'.
uninstall.sh for the demo, or to wire deactivation into a menu-bar app? Ask. Next we leave the build behind and explore the family security tools love: say "Lesson 7".