fleetd #492 follow-up: detect_supervisor must never read "could not tell" as "none" #499
Reference in New Issue
Block a user
Delete Branch "worker/492-followup-detect-unclear"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Follow-up to fleetd #492 / PR #495. Branch off
origin/worker/492-209647-1atdcd5052, not offmain.The defect
detect_supervisor()only readlaunchd_loaded/systemd_loaded. Anything short of a clean "yes" from either becamenone, andrequire_drivable_supervisor()acceptednonewithout question — the rawkill+nohupfallback then races a real supervisor and produces two daemons on one herdr session (the exact failure fleetd #492 exists to prevent).Two situations were landing in that unsafe
none:systemctl --user is-activeanswers "no" foractivating,deactivating,failed, and while an auto-restart is pending — every one of those is a host that IS under systemd and about to act again.*_installedalready knew the unit/plist existed; it was only ever consulted for a warning line, not by the decision.systemctlcannot reach the user bus over a non-lingering ssh session) — indistinguishable from a clean negative because bothsystemd_*probes threw stderr into/dev/null.The fix
detect_supervisornow returns a fifth answer,unclear, for both situations.systemd_loaded/systemd_installedcapturesystemctl's exit status and stderr separately via a temp file, and set their own*_ERROREDflag only when the call exits non-zero and wrote to stderr — a real tool failure, never a clean "not active"/"not installed" answer.nonenow means only: neither supervisor installed, neither loaded, neither probe errored.require_drivable_supervisordie()s onunclearexactly like it already does onambiguous, naming the specific supervisor and reason via a newSUPERVISOR_UNCLEAR_DETAILglobal.launchd_installed/launchd_loadedand the ambiguous/none logic,assert_single_daemon,count_daemon_pids, the systemd stop/start branches — all untouched, as scoped.Tests added (4)
test_detect_supervisor_systemd_installed_not_loaded_is_uncleartest_detect_supervisor_launchd_installed_not_loaded_is_uncleartest_detect_supervisor_systemd_probe_error_is_unclear— drives the REALsystemd_loadedbody through asystemctlstub onPATHthat exits non-zero and writes to stderr; asserts the result isunclear, notnone.test_require_drivable_supervisor_refuses_unclear— assertsdie()fires (subshell + exit status), and the refusal message names the systemd unit.Mutation proof
Baseline (unmodified tree at
dcd5052,fleetd/symlinked in so the recovery-pattern test could resolve source paths): 19 test functions invoked,exit=0, 3 pre-existingFAIL:lines (the predecessor's own mutation-proof cells — expected).Post-change: 23 test functions invoked,
exit=0, same 3 pre-existingFAIL:lines, unchanged.Three new guards, each mutated on the real file, proven applied with two greps (mutant text present + original text absent), suite run, then restored byte-identically with a green control run after each:
detect_supervisor(theli=1 && ld=0/si=1 && sd=0elifs). Grep 1 confirmed the mutant (if [ "$ld" = 1 ] && [ "$sd" = 1 ]now directly follows the function'slocalline). Grep 2 confirmed the removed text ("is installed ($LAUNCHD_PLIST exists) but is not currently loaded") was gone. Suite:exit=1,FAIL: systemd installed-but-not-loaded must read as unclear, not none: expected unclear, got none. Restored; controlexit=0.detect_supervisor. Grep 1 confirmed the mutant (the ambiguous check is now the firstif). Grep 2 confirmed the removedSYSTEMD_LOADED_ERRORED"... || ...SYSTEMD_INSTALLED_ERROREDtext was gone. Suite:exit=1,FAIL: a systemd probe error must read as unclear, not none: expected unclear, got none. Restored; controlexit=0.unclear)case arm fromrequire_drivable_supervisor(falls through to the generic*)catch-all, which stilldie()s but with a message that doesn't name the unit). Grep 1 confirmed the mutant (ambiguous)block now falls straight to*)). Grep 2 confirmed the specific wording ("actually drives this daemon") was gone. Suite:exit=1,FAIL: refusal message does not name the systemd unit it found installed-but-not-loaded— note the exit status alone does NOT catch this mutation (the catch-all still dies), only the message-content assertion does; that's why the test checks both. Restored; controlexit=0.Build
bash -n scripts/redeploy-fleetd.sh— clean.bash scripts/test-redeploy-fleetd.sh—exit=0.cd fleetd && mvn clean install—BUILD SUCCESS,Tests run: 1677, Failures: 0, Errors: 0, Skipped: 0.Caveat for review
I could not directly measure real
launchctl/systemctloutput text on this host (macOS, no systemd; constraints also forbid runninglaunchctl/systemctlagainst any real service). The systemd probe-error fix is based on documentedsystemctlbehavior (a clean "inactive"/"failed" answer has no stderr; a bus-unreachable failure does) and verified via asystemctlstub onPATH, not a real systemd host. I deliberately did NOT apply the same stderr-based error-detection tolaunchd_loaded:launchctl list <label>is documented to write "Could not find service..." to stderr on the completely normal "not loaded" case too, so the same heuristic there would risk misreading a genuinely-unsupervised host asunclear. Only the installed-but-not-loaded fix (not the stderr-error fix) was applied symmetrically to launchd.One more thing — same defect shape found elsewhere (NOT fixed, report only)
A probe whose failure to answer is recorded as a negative answer, found elsewhere in
scripts/andfleetd/src/main/java:scripts/rename-checkout.sh:308—[ -n "$(git -C "$OLD_PATH" status --porcelain 2>/dev/null)" ]gates a pre-flight "working tree is clean" check before a destructive move. Ifgit statusitself errors, stderr is discarded and the empty output reads as "clean," so the move proceeds without ever having verified cleanliness.scripts/rename-checkout.sh:95—launchd_loaded() { launchctl list "$LAUNCHD_LABEL" >/dev/null 2>&1; }treats any nonzero exit as "not loaded." A genuinelaunchctlfailure is indistinguishable from "legitimately not installed," and this boolean picks the stop path (killvs. safeunload -w) at line 324.fleetd/src/main/java/dev/ltms/fleet/herdr/PaneLocator.java:130-136—paneOwnsAnyOfcatchesHerdrExceptionand returnsfalse("pane vanished, skip it"), merging a genuine herdr/transport error with "this pane doesn't own the pid." Per the class's own javadoc (lines 22-23), a pid matching no pane falls through to loopback-trust and resolves as primary — an error here can trigger a worker→primary escalation.fleetd/src/main/java/dev/ltms/fleet/mcp/LsofPeerPidLookup.java:53-56— a clean "lsof found no peer" and "lsof failed to start/crashed" both return the identical sentinel-1.CallerResolver(fleetd #317, lines 240-247) relies on that sentinel to fall back to anonymous, so a tool crash and a clean negative are structurally the same signal at the source.fleetd/src/main/java/dev/ltms/fleet/mcp/LsofProcessCwdLookup.java:43-46— same shape: a clean "no cwd line found" and anlsofexec failure both returnnull.fleetd/src/main/java/dev/ltms/fleet/session/GitWorktrees.java:391-401—remoteUrlsLeakUserInfo(a credential-leak detector) catches anyRuntimeExceptionfromgit remote get-urland returnsfalse("no leak found"), which could mask a genuine credential-bearing remote URL if the read happens to fail for an unrelated reason.None of the above touched in this PR — scope was
scripts/redeploy-fleetd.shandscripts/test-redeploy-fleetd.shonly.