launchctl bootout --wait is a usage error on macOS 15, so boot-service install/update/restart fails
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 23k
- Forks
- 5.9k
- Avg merge
- 11h 14m
- Merged PRs (30d)
- 357
Description
Summary
On macOS 15, launchctl bootout --wait <service-target> is a usage error, so the boot service's stop step never runs. The old job keeps the label, and the strict bootstrap that follows fails with EIO. The result is that t3 service install, t3 update, and the post-update restart all fail on this OS, leaving the previous server running.
Environment
- macOS 15.7.9 (build 24G830), Darwin 24.6.0
Darwin Bootstrapper Version 7.0.0- t3
0.0.41-nightly.20260915.1752
Reproduction
$ launchctl bootout --wait gui/501/com.t3tools.t3code.service
Unrecognized target specifier.
Usage: launchctl bootout <domain-target> [service-path1, service-path2, ...] | <service-target>
<service-target> takes a form of <domain-target>/<service-id>.
Please refer to `man launchctl` for explanation of the <domain-target> specifiers.
$ echo $?
64
$ launchctl bootout gui/501/com.t3tools.t3code.service
$ echo $?
0
With a service already installed, t3 update <version> --yes then reports:
CliUpdateError: t3@<version> is installed but the background service could not be
updated: Background setup failed while starting the service (exit code 5).
and ~/.t3/userdata/logs/boot-service.log shows the two steps in order:
Background setup failed while stopping the installed launch agent (exit code 64).
Background setup failed while starting the service (exit code 5).
Background setup failed while restarting the service after a failed update (exit code 5).
Cause
launchdManager in apps/server/src/cloud/bootService.ts passes --wait to bootout in both its stop and deactivate steps:
args: ["bootout", "--wait", serviceTarget],
The accompanying comment reads "--wait (present on modern macOS, absent from the man page)". It is not present on macOS 15: launchctl rejects the whole invocation as a usage error rather than ignoring the flag. Because the step is optional: true, the failure is logged and the flow continues to bootstrap, which is exactly the EIO case the flag was added to avoid.
Suggested fix
Try the flag, fall back to a plain bootout, and wait for the label to leave the domain either way, so both old and new launchctl behave:
launchctl bootout --wait "$target" 2>/dev/null || launchctl bootout "$target" 2>/dev/null
i=0
while launchctl print "$target" >/dev/null 2>&1 && [ "$i" -lt 90 ]; do sleep 1; i=$((i + 1)); done
I am running that locally as a patch and t3 update now installs and restarts the service cleanly, including a downgrade back to the previous version. Happy to send it as a PR if you would like it in this shape.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in apps/server/src/cloud/bootService.ts at launchdManager’s stop and deactivate steps, then reproduce the macOS 15 behavior with launchctl bootout --wait and plain bootout. Verify the change by running the affected t3 service install or update flow and confirming the old label is removed before bootstrap and the service restarts successfully.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- macos, typescript
- Domain
- devops, operating-systems
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 76/100