Conversation
install.sh used `launchctl load`, the legacy launchctl API, which writes "Load failed: N: ..." to stderr but exits 0. `set -euo pipefail` therefore never fired, and the script went on to print "Installation complete" and "LifeOS Pulse menu bar is now running" whether or not anything started. Two failures had to be closed, not one: - `launchctl load` returning 0 on failure. Replaced with the domain-target API (`launchctl bootstrap gui/$UID`), which returns a real exit status, preceded by a `bootout` so a stale registration of the same label cannot fail the bootstrap with EX 5 (Input/output error). - A bootstrap succeeding while nothing runs. launchd accepts a job whose plist names a nonexistent program and returns 0, which is exactly the case where the app bundle is missing from ~/Applications. Added a poll on the deployed binary so success is claimed only once a process exists, and a failure path that points at the menubar log and prints the job's last exit code. Tested on macOS 15 (Darwin 25.6.0), Apple silicon: red against a label pointing at a nonexistent binary (exit 1), green against a real one (exit 0), and end to end against a live install, which now completes with no "Load failed" line and leaves the job at state = running with last exit code = (never exited), where it previously showed 78. Fixes danielmiessler#2068 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
m4rkolson
force-pushed
the
fix/menubar-install-verify-launchctl
branch
from
September 5, 2026 18:54
e55116e to
fbee4b1
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #2068.
What this changes
LIFEOS/PULSE/MenuBar/install.shclaimed a successful install whether or not the menu bar started. Step 6 usedlaunchctl load, the legacy launchctl API, which writesLoad failed: N: ...to stderr and still exits0. Theset -euo pipefailon line 5 never fired, so the script printedInstallation completeandLifeOS Pulse menu bar is now runningon top of a job that had not started.Why the obvious one-line fix is not enough
Checking the exit status of the load call closes only half of it. There are two independent failures here and a probe that catches the first still goes green on the second:
launchctl loadreturns 0 on failure. Fixed by moving to the domain-target API,launchctl bootstrap gui/$UID, which returns a real exit status. Abootoutruns first so a stale registration of the same label cannot fail the bootstrap with EX 5 (Input/output error) on reinstall — which is the case step 4'slaunchctl unloadwas already trying, and failing, to handle.A bootstrap can succeed while nothing runs. launchd accepts a job whose plist names a program that does not exist and returns
0. That is precisely the failure this started from: the plist pointed at~/Applications/LifeOS Pulse.app/...on a machine where the bundle had never been built, launchd respawned and failed with78(EX_CONFIG) indefinitely, and the installer had reported success. So the script now polls for the deployed binary and only claims success once a process exists.The failure path points at
menubar-stderr.logand prints the job's last exit code, since when the program itself cannot be executed those log files are never created and the obvious next debugging step turns up an empty directory.Testing
macOS 15 (Darwin 25.6.0), Apple silicon.
bootstrapreturns 0, process check fails, script exits 1install.shagainst a live installLoad failedline, exits 0;launchctl printreportsstate = running,last exit code = (never exited), where the same box previously showed78Both throwaway labels were booted out and their plists removed after the run.
Notes
BINARY_NAME, whichbuild.shalready defines butinstall.shdid not, so the process check has a path to match against rather than a bare app name.Loaded <label>becomingBootstrapped <label>.build.sh, the plist, or the Swift source.