[Bug]: macOS: a removed launchd job strands the agent for good — kickstart loops on 113 while SMAppService still reports Enabled
- Dominant language
- Rust
- Stars
- 21k
- Forks
- 675
- Avg merge
- 2d 5h
- Merged PRs (30d)
- 172
Description
**Component:** Agent / GUI (login-item registration)
**Version:** 0.8.3
**OS:** macOS 26.6.2 (25G83), arm64
**Area:** Menu bar / background agent, Settings / configuration (TOML)
## What happened
Something removed the agent's launchd job (`launchctl bootout` / `unload` — an
app cleaner, an uninstaller sweep, or a manual restart attempt). The agent
exited and never came back: not by kickstart, not after quitting and relaunching
the GUI, not after a fresh login. Every settings change then lands in
`config.toml` but is never applied, and the window shows the fail-closed notice
"saved, but the agent is not running, so it has not been applied yet". Its
primary button — "Relaunch OpenLogi" — restarts the *GUI*, so it changes
nothing about this state.
The GUI's own log shows the loop, once every `SPAWN_RETRY_PERIOD`:
```
WARN openlogi_desktop::services::ipc::launch: launchctl kickstart failed
target=gui/501/org.openlogi.agent.service status=exit status: 113
stderr=Could not find service "org.openlogi.agent.service" in domain for user gui: 501
WARN openlogi_desktop::services::ipc::launch: launchctl kickstart failed (second rung, after ensure_registered)
target=gui/501/org.openlogi.agent.service status=exit status: 113 ...
WARN openlogi_desktop::services::ipc::launch: agent is not running and the supervised
launch paths could not start it — leaving it down status=Enabled
```
## Root cause
`SMAppService` reads the Background Task Management record; `launchctl`
addresses the launchd domain. A `bootout` removes the job and leaves the
record, so the two disagree — and every recovery path in the GUI is keyed on
the record:
- `kickstart_registered_agent()` (`services/ipc/launch.rs`) sees
`status() == Enabled`, runs `launchctl kickstart`, and gets exit 113
("Could not find service"). It returns `false` — with no way to say *why*.
- The second rung calls `registration::ensure_registered()`. Its rule
(`platform/registration/macos.rs::ensure_action`) is `Enabled` + version
marker current ⇒ `None`: nothing to do. So nothing is done.
- Registering again would not help on its own either: on a surviving record
`SMAppService.register` returns `kSMErrorAlreadyRegistered` (which
`backend::register` forgives as success) without submitting a job. Only the
unregister-then-register dance that `EnsureAction::Reregister` already
performs rebuilds one.
So the state is self-sustaining: the record keeps the repair from running, and
without the repair the job never comes back. A reboot does not help — the
record is what login start reads.
Timeline from this machine (unified log + `~/.local/state/openlogi/agent.*.log`):
```
22:49:46.512 agent : releasing the input hook and exiting reason="shutdown signal"
22:49:46.557 launchd: [gui/501/org.openlogi.agent.service] caller removing service: caller = launchctl[89099]
22:49:46.599 launchd: [gui/501] removing service: org.openlogi.agent.service
23:09:31 → GUI : kickstart 113 / kickstart 113 / "leaving it down" status=Enabled, every 30 s
```
`launchctl print gui/501/org.openlogi.agent.service` → "Could not find service",
while `launchctl print-disabled gui/501` still lists the label as `enabled` and
`SMAppService` reports `Enabled`.
## Workaround
Force the re-registration dance the GUI declines to run, by making the version
marker read as stale:
```sh
rm ~/.local/share/openlogi/registration-version
# quit and relaunch OpenLogi
```
The next launch takes `ensure_action(Enabled, stale) → Reregister`, launchd gets
a job again, and the agent starts (verified on this machine — agent, hook, tray
and overlay all came back within seconds).
## Expected
`launchctl` answering "no such service" for a registration that reports
`Enabled` should rebuild the registration, not be retried forever.
Contributor guide
No contributing guide indexed for this repository
Research direction
Start in services/ipc/launch.rs with kickstart_registered_agent(), then trace platform/registration/macos.rs and ensure_action, including the existing EnsureAction::Reregister path. Reproduce the Enabled-but-missing-service state and make the recovery stop retrying exit 113 forever; verify that the launchd job and agent return without requiring a stale registration-version marker.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- macos, rust
- Domain
- desktop, operating-systems
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 58/100