← back to Dw Launchd Canary
canary: running daemon with live PID is OK despite stale LastExitStatus
813d58785c4cee6ccefe0e7e663235f8a757049d · 2026-06-17 14:28:04 -0700 · Steve Abrams
A KeepAlive daemon (ollama-henry) that is running right now isn't failing just
because launchctl shows a nonzero LastExitStatus from a prior respawn cycle.
Parse the PID and treat a live PID as OK — kills the false-positive that flagged
ollama-henry and signal-exited-but-running daemons as FAILING/KILLED.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Files touched
Diff
commit 813d58785c4cee6ccefe0e7e663235f8a757049d
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed Jun 17 14:28:04 2026 -0700
canary: running daemon with live PID is OK despite stale LastExitStatus
A KeepAlive daemon (ollama-henry) that is running right now isn't failing just
because launchctl shows a nonzero LastExitStatus from a prior respawn cycle.
Parse the PID and treat a live PID as OK — kills the false-positive that flagged
ollama-henry and signal-exited-but-running daemons as FAILING/KILLED.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---
canary.mjs | 16 ++++++++++------
1 file changed, 10 insertions(+), 6 deletions(-)
diff --git a/canary.mjs b/canary.mjs
index b6eaedd..899a815 100644
--- a/canary.mjs
+++ b/canary.mjs
@@ -43,21 +43,24 @@ const THRESH = manifest.consecutive_fail_threshold || 2;
const plists = fs.readdirSync(LA).filter(f => /^com\.steve\..+\.plist$/.test(f));
function launchctlInfo(label) {
- // returns { loaded, exit } — exit is the macOS-raw LastExitStatus or null
+ // returns { loaded, running, exit } — exit is the macOS-raw LastExitStatus or null.
+ // running = a live PID is present; a KeepAlive daemon that's running now is NOT
+ // failing even if LastExitStatus is a stale nonzero from a prior respawn cycle.
let line = '';
try { line = execSync(`launchctl list ${label} 2>/dev/null`, { encoding: 'utf8' }); } catch { line = ''; }
if (!line.trim()) {
// fall back to the column form (some labels only show in the table)
try {
const tbl = execSync(`launchctl list 2>/dev/null | grep -F ${label}`, { encoding: 'utf8' }).trim();
- if (!tbl) return { loaded: false, exit: null };
+ if (!tbl) return { loaded: false, running: false, exit: null };
const cols = tbl.split(/\s+/); // PID STATUS LABEL
const ex = cols[1] === '-' ? null : parseInt(cols[1], 10);
- return { loaded: true, exit: Number.isNaN(ex) ? null : ex };
- } catch { return { loaded: false, exit: null }; }
+ return { loaded: true, running: cols[0] !== '-' && /^\d+$/.test(cols[0]), exit: Number.isNaN(ex) ? null : ex };
+ } catch { return { loaded: false, running: false, exit: null }; }
}
const m = line.match(/"LastExitStatus"\s*=\s*(\d+)/);
- return { loaded: true, exit: m ? parseInt(m[1], 10) : null };
+ const p = line.match(/"PID"\s*=\s*(\d+)/);
+ return { loaded: true, running: !!p, exit: m ? parseInt(m[1], 10) : null };
}
const results = [];
@@ -67,12 +70,13 @@ for (const f of plists) {
// is it Disabled=true in the plist itself? (deliberate pause, keeps the .plist name)
let disabled = false;
try { disabled = /<key>Disabled<\/key>\s*<true\/>/.test(fs.readFileSync(path.join(LA, f), 'utf8')); } catch {}
- const { loaded, exit } = launchctlInfo(label);
+ const { loaded, running, exit } = launchctlInfo(label);
let verdict = 'OK', reason = 'loaded, exit 0';
if (disabled) { verdict = 'PAUSED'; reason = 'Disabled=true in plist (deliberate)'; }
else if (expectedUnloaded.has(short)) { verdict = 'EXPECTED_OFF'; reason = 'intentionally unloaded (manifest expected_unloaded)'; }
else if (!loaded) { verdict = 'DROPPED'; reason = 'plist on disk but NOT in launchctl — silently unloaded (drift/reboot/failed bootstrap)'; }
+ else if (running) { verdict = 'OK'; reason = 'running now (live PID) — a stale LastExitStatus is prior-cycle residue, not a failure'; }
else if (exit === null) { verdict = 'OK'; reason = 'loaded, no exit status yet (not run since load)'; }
else if (exit === 0) { verdict = 'OK'; reason = 'loaded, exit 0'; }
else if (exit % 256 !== 0) {
← 0a4edf7 Add gated-plist-install batcher (Officer Council #5)
·
back to Dw Launchd Canary
·
chore: macstudio3 migration — reconcile from mac2 + repoint fac0591 →