Observation

launchctl Override Rows Survive Userland Label Removal

fleet-opslaunchctlmacos

launchctl Override Rows Survive Userland Label Removal

Context

FLT-0255 and FLT-0276 renamed the local fleet LaunchAgent mirror from com.orbit. labels to com.camberzero.fleet. labels. After the rename, launchctl print gui/501/com.orbit.<label> reported no live services, but launchctl print-disabled gui/501 still listed three stale labels as enabled:

  • com.orbit.shepherd-feed-watch
  • com.orbit.ops-deputy-loop
  • com.orbit.ops-command-churn-alarm

This observation captures the local launchd behavior verified during FLT-0279.

Observation

On this host/build, current macOS launchd keeps override-only rows in /private/var/db/com.apple.xpc.launchd/disabled.<uid>.plist, and ordinary userland lifecycle commands do not clear them once written.

Verified facts:

1. The stale com.orbit.* labels are not loaded services.

  • launchctl print gui/501/com.orbit.shepherd-feed-watch => Could not find service
  • same for ops-deputy-loop and ops-command-churn-alarm
  • 2. The labels still exist as keys in the root-owned override DB:

  • /private/var/db/com.apple.xpc.launchd/disabled.501.plist
  • 3. Their stored values are boolean false, which launchctl print-disabled renders as enabled. 4. A throwaway probe label reproduced the same persistence pattern:

  • bootstrap a temporary plist
  • disable then enable the label
  • bootout the label
  • print-disabled still shows the label as enabled
  • 5. Alternate userland paths do not clear the row either:

  • launchctl remove
  • launchctl load -w
  • launchctl unload
  • launchctl unload -w
  • 6. This host's launchctl manpage/help exposes no reset-disabled or equivalent row-prune command.

Interpretation

There are two distinct states that look similar in launchd output but are not the same thing:

  • live LaunchAgent state
  • persisted override-row state

Renaming or removing a LaunchAgent label can leave the second behind even after the first is gone. Once that happens, the residue is no longer a launch-surface problem; it is a root-owned override-database problem.

That means future audits must not treat print-disabled entries alone as proof of a live service. First ask whether launchctl print gui/<uid>/<label> finds anything at all.

Countermeasure

FLT-0279 landed a mirrored helper:

  • scripts/fleet-runtime-helpers/launchd-disabled-prune

The helper is dry-run by default, creates a backup before mutation, and prunes only boolean-false rows unless forced. It exists because the supported launchctl surface can diagnose this state but not clear it.

Maturity Rationale

M2 rather than M1 because the behavior was reproduced with both:

  • the real stale com.orbit.* labels on the host
  • a fresh throwaway probe label created solely to test launchd semantics

That is enough to treat the pattern as observed and repeatable on this host.