danielmiessler / danielmiessler/LifeOS

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

Open
#2,068 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
19k
Forks
2.5k
Avg merge
8d 17h
Merged PRs (30d)
1

Description

### Version

LifeOS 7.40.4 / Pulse menu bar installer

### What is broken

`install.sh` for the Pulse menu bar announces a successful install whether or not the menu bar actually started. Step 6 calls `launchctl load`, the legacy launchctl API, which writes `Load failed: N: ...` to stderr but still exits `0`. The `set -euo pipefail` on line 5 therefore never fires, and the next four lines print `Loaded com.lifeos.pulse-menubar`, `=== Installation complete ===`, and `LifeOS Pulse menu bar is now running.`

The consequence is a silent install failure. On a machine where the LaunchAgent cannot start — for instance the plist names a program path that does not exist — the installer reports a healthy install, the user gets no menu bar icon, and nothing points at a cause. The job sits in launchd being respawned and failing; `launchctl list` shows `78` (EX_CONFIG) beside the label, but the installer never looks and never says so. The failure reads as "the menu bar is flaky and sometimes missing" rather than "the menu bar has never once launched."

The stdout/stderr log paths the plist declares are also a false trail here: when the program itself cannot be executed, those files are never created, so the obvious next debugging step turns up nothing.

### Where (file:line)

`LIFEOS/PULSE/MenuBar/install.sh:73` — `launchctl load "$PLIST_DST"`
Success is claimed unconditionally at `:74` and `:77-79`.

In-repo path: `LifeOS/install/LIFEOS/PULSE/MenuBar/install.sh`

### Repro on a clean tree

```shell
# Against a label that is already bootstrapped — i.e. the reinstall case that
# step 4's `launchctl unload` is meant to have handled.

$ launchctl load ~/Library/LaunchAgents/com.lifeos.pulse-menubar.plist; echo "exit: $?"
Load failed: 5: Input/output error
Try running `launchctl bootstrap` as root for richer errors.
exit: 0

# And therefore set -e cannot see the failure:

$ bash -c 'set -euo pipefail; launchctl load ~/Library/LaunchAgents/com.lifeos.pulse-menubar.plist; echo "SCRIPT CONTINUED"'
Load failed: 5: Input/output error
Try running `launchctl bootstrap` as root for richer errors.
SCRIPT CONTINUED
```

Running the shipped `install.sh` on a box in this state prints `Load failed: 5: Input/output error` in the middle of step 6 and then `=== Installation complete === / LifeOS Pulse menu bar is now running.`, exiting `0`.

### Negative control

Two independent failures both stay green on unpatched 7.40.4, which is why an exit-status check on the load call alone is not sufficient:

**1. The load call reports failure and returns success.** On unpatched 7.40.4, `launchctl load` against an already-bootstrapped label prints `Load failed: 5: Input/output error` and exits `0`. Under `set -euo pipefail` the script continues; the transcript above is the unpatched behaviour, with no fix applied.

**2. A bootstrap can succeed while nothing runs.** Verified with a throwaway label whose plist named a nonexistent program: `launchctl bootstrap` accepted the job and returned `0`, and no process ever appeared. So a script that checks only the exit status of its load/bootstrap call still reports success on the exact failure mode described above, where the app bundle is missing from `~/Applications`.

Any check that does not confirm a running process stays green through both. A probe covering only case 1 would have been green on the install that prompted this report.

Related, same symptom family and a different cause: #1400 (`launchd exit 78` from an unsubstituted placeholder in `com.lifeos.pulse.plist`).

### Suggested fix

Replace the legacy `launchctl load` with the domain-target API, which returns a real exit status, and then confirm a process actually came up before claiming success. `bootout` first so a stale registration of the same label cannot fail the bootstrap with EX 5.

```diff
-launchctl load "$PLIST_DST"
-echo " Loaded $PLIST_LABEL"
+DOMAIN="gui/$(id -u)"
+launchctl bootout "$DOMAIN/$PLIST_LABEL" 2>/dev/null || true
+
+if ! launchctl bootstrap "$DOMAIN" "$PLIST_DST"; then
+ echo " ERROR: launchctl bootstrap failed for $PLIST_LABEL" >&2
+ echo " The menu bar is NOT installed. Plist: $PLIST_DST" >&2
+ exit 1
+fi
+echo " Bootstrapped $PLIST_LABEL"
+
+BINARY_PATH="$APP_DEST/Contents/MacOS/$BINARY_NAME"
+for _ in $(seq 1 10); do
+ if pgrep -f "$BINARY_PATH" >/dev/null 2>&1; then
+ RUNNING=1
+ break
+ fi
+ sleep 0.5
+done
+
+if [ "${RUNNING:-0}" -ne 1 ]; then
+ echo " ERROR: $PLIST_LABEL was bootstrapped but no process is running." >&2
+ echo " Check $HOME_DIR/.claude/LIFEOS/PULSE/logs/menubar-stderr.log" >&2
+ launchctl print "$DOMAIN/$PLIST_LABEL" 2>&1 | grep -E "last exit code|state =" >&2 || true
+ exit 1
+fi
```

Tested on macOS 15 (Darwin 25.6.0), Apple silicon:

- Red: throwaway label pointing at a nonexistent binary — bootstrap returns `0`, the process check fails, script exits `1`.
- Green: throwaway label pointing at a real long-running binary — exits `0`.
- End to end: patched `install.sh` against a real install completes with no `Load failed` line, exits `0`, and `launchctl print` reports `state = running` with `last exit code = (never exited)`, where the same box previously showed `78`.

PR follows.

Contributor guide

No contributing guide indexed for this repository

Research direction

Start with LifeOS/install/LIFEOS/PULSE/MenuBar/install.sh around lines 73-79 and review the existing launchctl flow. Reproduce the already-bootstrapped and nonexistent-binary cases on macOS, then verify that a real running menu bar is required for success and that failed starts exit nonzero with useful diagnostics.

Written by the indexing model from the issue text.

Assessment

Tech stack
bash, macos
Domain
devops, operating-systems
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
82/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.