fix: verify launchd server startup after install - #63
Conversation
|
I checked this out and ran the verification function against real launchd jobs on macOS 26.5.2. The immediate-exit detection does work, but I don't think the 1s budget catches the failure mode this is aimed at. Timings from this repo's venv:
At one second a healthy server and a doomed one look identical, because both are a live process still importing, so I bootstrapped a throwaway agent whose program stays alive 3.8s and then exits 3, to mimic the port-conflict timing, and called So The instant-exit case is fine. launchd registers it at about 0.32s: and the On the tests: every case passes Two other things I noticed:
Minor: Suggestion: keep the exit-code and signal rejection as they are, and make the endpoint probe the actual success signal with a bound nearer 8-10s. The healthy path returns as soon as the port answers (3.4s here), so it isn't a fixed wait, and the failure path then produces a real error instead of an optimistic success line. A test that steps a fake clock through the loop with |
4d7b253 to
7cd8d3a
Compare
|
@Jeomon Thank you for checking this against real launchd jobs and for the detailed timings. Your point about a 1s process-state check being unable to distinguish a healthy server from a doomed one was exactly right. I updated the PR so startup success now requires the configured endpoint to accept connections within a bounded 10s window, returning as soon as it is ready. The polling path is exercised with a fake clock, including deadline/sleep behavior rather than only a zero-timeout single iteration. I also incorporated the other failure-path points you called out: verified failures clean up the broken KeepAlive agent, transient Real launchd acceptance now has the healthy direct executable ready in about 2.7-3.4s; a delayed exit is rejected around 4.3-4.6s; SIGTERM and an explicit port conflict are rejected correctly; rollback restores the prior plist byte-for-byte and restarts the previously loaded service; and a same-port force reinstall succeeds after allowing the old socket to release. One residual note: an isolated cold Thanks again for the concrete reproduction and review; it materially improved the startup acceptance behavior without broadening the PR into TCC/signing or unrelated launchd work. |
Summary
launchctl bootstrapsuccess as server startup success.launchctl printfailures while polling and only parse top-level launchd fields.--forcereinstall fails, including rollback after startup verification failure.--force, use a short bounded retry when determining whether the old job is loaded so one transientlaunchctl printfailure is not misclassified as unloaded.Root cause
installpreviously checked only the return code fromlaunchctl bootstrap. Bootstrap confirms that launchd accepted the job definition; it does not guarantee that the server process stayed alive or reached its listen endpoint. A short process-state-only check also cannot reliably distinguish a healthy server from a process that is still importing and will fail shortly afterward.Validation
14 passed347 passedgit diff --check: cleancom.macos-mcp.serverwas left unchanged; final check showed it running with the same plist SHA256 and127.0.0.1:8100accepting connectionsResidual cold
uvxnoteAn isolated cold
uvxbootstrap exceeded the 10-second readiness window because dependency download dominated startup; prewarming itself took about 31s. This PR intentionally does not change TCC, so isolateduvxhealthy readiness timing was not measured after that prewarm. The installed direct executable reached readiness in about 2.7-3.4s.Non-changes