fix(agent): re-arm a crash respawn instead of going dormant - #1619
Merged
Merged
Conversation
With launch_at_login off, the macOS dormancy gate read every launchd start as a login the user opted out of and left 60 s later with the exit(0) launchd never respawns. A crash respawn takes the same path, since the service plist gives login and crash one trigger, so a single hook-watchdog exit(78) on a lid close ended remapping until the GUI was opened by hand. Arming now records the login session (kernel boot session UUID plus audit session id) in the runtime dir. Every final exit - tray Quit, uninstall, SIGTERM - erases it; a handover to a scheduled successor keeps it. A start that finds its own session recorded re-arms at once; a login finds a stale record and stays dormant. Refs #952
|
…uit fallback Two review findings on #1619. The record was written in place, so a kill mid-write could leave a truncated file the next start reads as no record; it is now staged beside the record and renamed into place. And the tray-Quit fallback in shutdown.rs, taken when the lifecycle that would clear the record has already ended, exited without clearing it; it now does, so a Quit whose core had died is still followed by a dormant start rather than a re-arm.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
With
launch_at_loginoff, the macOS dormancy gate treats every launchd start as a login the user opted out of, and leaves 60 s later with theexit(0)launchd never respawns. A crash respawn takes the same path, because the service plist gives login and crash one trigger (SuccessfulExitimpliesRunAtLoad). So one non-zero exit — the hook watchdog'sexit(78)on a sleep transition (#952), a panic — silently ended remapping until the GUI was opened by hand. On the reporting host that was 3.5 h on 2026-09-28 and the rest of the night on 2026-09-26; the agent log shows the sequenceHID CGEventTap lifecycle did not make progress→launch_at_login is off — dormant until a client demands arming→no arming demand — exiting until wanted.The gate now asks the login session instead of guessing at launchd's reason. Arming records the session; a start that finds its own session recorded follows an unclean exit or a handover and re-arms at once; a login finds nothing or a stale record and stays dormant. With #1282 (the watchdog half) this is the second half of #952.
Changes
openlogi-agent: newlifecycle/armed_session.rs. Arming records the login session — kernel boot session UUID (kern.bootsessionuuid) plus audit session id (SessionGetInfo) — in the runtime dir; the boot half is needed because audit ids repeat across boots.Running::shut_down(tray Quit, uninstall, SIGTERM/SIGINT) clears the record before leaving;Running::hand_over(Input Monitoring relaunch) andexit_after_replacement_teardown(binary update) leave it for the successor.Booted::gatere-arms whenarmed_session::rearm()is true, before the dormant wait. New macOS-only dependenciesobjc2-security(workspace table,AuthSessionfeature) andlibc;Cargo.lockgains only those two edges, no version moves.docs/DECISIONS.md: why the gate asks the session rather than launchd, why a second crash-only plist (KeepAlive = {Crashed: true}) was rejected (it does not respawnexit(78)or a panic'sexit(101), and it reintroduces the two-label registration the 2026-08 lifecycle work removed), and why SIGTERM is final..agents/rules/objc-ffi.md: the newunsafesite and the new framework crate in the inventory. The xtask plist doc comment points at the decision.Behaviour changes, all with
launch_at_loginoff: a launchd respawn after a crash re-arms; the successor after a Homebrew or in-app update re-arms even with no GUI running (previously it went dormant and exited after 60 s); the relaunch after an Input Monitoring grant re-arms likewise. Login starts are unchanged: a new login has a new audit session id.Testing
Run on macOS 26 (arm64) with
RUSTFLAGS="-D warnings", on the tree rebased onto master 5cca3f5:cargo fmt --all -- --checkcargo clippy --workspace --all-targets -- -D warningscargo test --workspaceRUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps --document-private-items --exclude openlogi-ui --exclude openlogi-desktop --exclude openlogi-overlay --exclude openlogi-agentcargo xtask ci clippy-windows(thehand_over/leavesplit is what the Windows lane checks; an earlier shape failed there with a dead-code variant)Not run:
tests (linux),msrv,cargo-deny(CI).Runtime, a debug agent in an isolated XDG sandbox (dev profile,
launch_at_login = false,capture_mouse_events = false, socket path kept under the 104-bytesun_pathlimit): fresh start goes dormant and writes no record; aClientKind::Guideclaration arms it and writes the record;SIGKILL(stand-in forexit(78), no lifecycle exit runs) leaves the record and the next start logsre-arming;SIGTERMclears the record and the next start goes dormant; a planted record from another audit session id or another boot UUID leaves the start dormant.Not runtime-tested on the packaged, launchd-supervised build, and no real lid-close cycle. To test on hardware: with
launch_at_loginoff and the agent armed,kill -KILL $(pgrep -x openlogi-agent); launchd respawns it and the agent log should readlaunch_at_login is off, but this login armed an agent that did not quit — re-arminginstead of the dormant line. Then Quit from the tray andlaunchctl kickstart gui/$(id -u)/org.openlogi.agent.service: the log should readdormant until a client demands armingand the agent should leave after 60 s.One test,
the_current_session_is_readable_and_round_trips, asserts that the test process has an audit session and thatkern.bootsessionuuidreads back; it passes on a logged-in Mac and is expected to on the macOS runner.Refs #952