The deprecated `launchctl load -w` returns exit code 0 even when it
cannot actually reload a service whose label is already in launchd's
in-memory state. It prints `Load failed: 5: Input/output error` to
stderr but exits 0, so the install path interprets it as success and
continues — silently leaving the running daemon on whatever binary
was first loaded, even though the on-disk plist now points elsewhere.
The consequence: every macOS user running `brew upgrade numa` rewrites
the plist to point at the new binary, but launchctl never actually
loads it. They think they upgraded; they're still running the old
version. Neither #41 (cross-platform CA trust) nor #40 (self-referential
backup) would actually take effect for them until they manually run:
sudo launchctl bootout system /Library/LaunchDaemons/com.numa.dns.plist
sudo launchctl bootstrap system /Library/LaunchDaemons/com.numa.dns.plist
The fix uses the modern API symmetrically across all three call sites:
- install_service_macos: bootout (best-effort cleanup, no-op on first
install) → bootstrap → wait for readiness → configure DNS
- install_service_macos rollback path: bootout instead of `unload`
- uninstall_service_macos: bootout BEFORE remove_file (the modern API
needs the plist file path as the specifier; doing it after remove
would leave the service in memory until reboot)
No new tests — this is a shell-call substitution with no logic to
unit-test. Verified manually on macOS: `sudo numa install` no longer
prints `Load failed`, and the daemon is correctly running the binary
the plist points at.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>