fix: macOS use launchctl bootout/bootstrap instead of deprecated load #42
Reference in New Issue
Block a user
Delete Branch "fix/launchctl-bootout-bootstrap"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Replace the deprecated
launchctl load -w/unload -wAPI with the modernbootout/bootstrappair across all three call sites ininstall_service_macosanduninstall_service_macos.The bug
launchctl load -wreturns exit code 0 even when it cannot actually reload a service whose label is already in launchd's in-memory state. It printsLoad failed: 5: Input/output errorto stderr but exits 0, so the calling code (install_service_macos) interprets it as success and continues.The consequence: every
numa installrewrites the on-disk plist to point at a new binary path, but launchctl's in-memory state still references the binary that was first loaded. The daemon stays on the old binary forever, despite "successful" install output.This affects every macOS user upgrading via Homebrew. Without this fix, brew users who
brew upgrade numafrom v0.10.0 to v0.10.1 would silently keep running v0.10.0 — neither the cross-platform CA trust fix (#41) nor the self-referential backup fix (#40) would actually take effect on their machines until they manually ran:Diagnosed during this evening's dogfood session — every
sudo numa installwe ran produced theLoad failed: 5line while exiting cleanly. The error message itself saysTry running launchctl bootstrap as root for richer errors— macOS is literally telling us to use the modern API.The fix
Use
bootout+bootstrapsymmetrically across three call sites:install_service_macos(load)launchctl load -w PLISTlaunchctl bootout system PLIST(best-effort cleanup) →launchctl bootstrap system PLISTinstall_service_macos(rollback when port 53 in use)launchctl unload PLISTlaunchctl bootout system PLISTuninstall_service_macoslaunchctl unload -w PLIST(afterremove_file)launchctl bootout system PLIST(BEFOREremove_file, so the file still exists as the specifier)The deprecated
load/unloadcommands have been deprecated since macOS 10.10 (Yosemite, 2014). Every supported macOS version has the modern API.Test plan
#[cfg(target_os = "macos")]; macOS: full build + test)sudo ./target/release/numa installno longer printsLoad failed: 5: Input/output errorsudo numa installover an existing install completes cleanly with no errorExisting DNS backup preservedand PR #41'sTrusted Numa CA in system keychainstill fire correctly (this fix is orthogonal to both)Why this is its own PR
Different concern from #40 (DNS backup integrity) and #41 (CA trust). Different code path (
install_service_macos/uninstall_service_macosvsinstall_macos/install_windows/install_linux). Easier to revert independently if needed.Why this should ship with v0.10.1
Without it, brew users upgrading from v0.10.0 to v0.10.1 will silently fail to actually load the new binary, negating both #41 and #40 for the upgrade path. v0.10.1 should be deferred until this lands.
🤖 Generated with Claude Code