Skip to content

Fix Pulse menu bar installer reporting success on a failed load - #2069

Open
m4rkolson wants to merge 1 commit into
danielmiessler:mainfrom
m4rkolson:fix/menubar-install-verify-launchctl
Open

m4rkolson wants to merge 1 commit into
danielmiessler:mainfrom
m4rkolson:fix/menubar-install-verify-launchctl

Conversation

@m4rkolson

@m4rkolson m4rkolson commented Sep 5, 2026

Copy link
Copy Markdown

Fixes #2068.

What this changes

LIFEOS/PULSE/MenuBar/install.sh claimed a successful install whether or not the menu bar started. Step 6 used launchctl load, the legacy launchctl API, which writes Load failed: N: ... to stderr and still exits 0. The set -euo pipefail on line 5 never fired, so the script printed Installation complete and LifeOS Pulse menu bar is now running on 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:

  1. launchctl load returns 0 on failure. Fixed by moving to the domain-target API, launchctl bootstrap gui/$UID, which returns a real exit status. A bootout runs 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's launchctl unload was already trying, and failing, to handle.

  2. 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 with 78 (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.log and 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.

Case Setup Result
Red throwaway label, plist names a nonexistent binary bootstrap returns 0, process check fails, script exits 1
Green throwaway label, plist names a real long-running binary exits 0
End to end patched install.sh against a live install no Load failed line, exits 0; launchctl print reports state = running, last exit code = (never exited), where the same box previously showed 78

Both throwaway labels were booted out and their plists removed after the run.

Notes

  • Adds BINARY_NAME, which build.sh already defines but install.sh did not, so the process check has a path to match against rather than a bare app name.
  • Behaviour on a healthy install is unchanged apart from Loaded <label> becoming Bootstrapped <label>.
  • No change to build.sh, the plist, or the Swift source.

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
m4rkolson force-pushed the fix/menubar-install-verify-launchctl branch from e55116e to fbee4b1 Compare September 5, 2026 18:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Pulse menu bar install.sh always reports success: launchctl load exits 0 on failure, so set -e never fires and a never-started menu bar looks installed

1 participant