pingdotgg / pingdotgg/t3code

launchctl bootout --wait is a usage error on macOS 15, so boot-service install/update/restart fails

Open
#11,995 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

accepted bug via-triage
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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.