← back to Ticket System
guard ticket-board pm2 jlist behind rpc.sock-reachability precheck (TK-10970 fracture fix)
89c7ae5e114b8cbaec4a6de41cc419bcaed6b991 · 2026-08-30 17:17:32 -0700 · Steve Abrams
Files touched
Diff
commit 89c7ae5e114b8cbaec4a6de41cc419bcaed6b991
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Sun Aug 30 17:17:32 2026 -0700
guard ticket-board pm2 jlist behind rpc.sock-reachability precheck (TK-10970 fracture fix)
---
server.js | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/server.js b/server.js
index ed2ae5d1..3976983d 100644
--- a/server.js
+++ b/server.js
@@ -18,9 +18,30 @@ function readJson(req, cb) { let b = ''; req.on('data', d => { b += d; if (b.len
// ── live "running" signal: online pm2 processes + live claude CLI sessions ──
// Cached 5s so N polling browsers don't each spawn a `pm2 jlist` on a busy box.
+// TK-10970 daemon-fracture guard: a bare `pm2 jlist` FORKS a new "God" daemon when the rpc.sock is
+// transiently unreachable (not only when pm2 is absent). This always-up board must not be a source
+// of socket-less orphan daemons, so we only jlist when the rpc.sock exists AND a live God daemon
+// holds it; otherwise return the cached/empty pm2 list (never fork).
+const PM2_HOME_TS = process.env.PM2_HOME || require('path').join(require('os').homedir(), '.pm2');
+function pm2DaemonReachable() {
+ try {
+ const rpc = require('path').join(PM2_HOME_TS, 'rpc.sock');
+ if (!require('fs').existsSync(rpc)) return false;
+ const { execSync } = require('child_process');
+ const held = execSync(`lsof -nP ${JSON.stringify(rpc)} 2>/dev/null`, { timeout: 4000 }).toString().trim();
+ if (!held) return false;
+ const ps = execSync('ps ax -o pid,command 2>/dev/null', { maxBuffer: 8 * 1024 * 1024, timeout: 4000 }).toString();
+ return ps.split('\n').some(l => /PM2 v[\d.]+: God Daemon/.test(l) && l.includes(PM2_HOME_TS));
+ } catch (_) { return false; }
+}
let runCache = { ts: 0, data: { pm2: [], sessions: 0, at: null } };
function getRunning(cb) {
if (Date.now() - runCache.ts < 5000) return cb(runCache.data);
+ if (!pm2DaemonReachable()) {
+ // socket not reachable — do NOT fork a daemon; serve last-known (or empty) pm2 list
+ runCache = { ts: Date.now(), data: { pm2: runCache.data.pm2 || [], sessions: runCache.data.sessions || 0, at: new Date().toISOString() } };
+ return cb(runCache.data);
+ }
exec('pm2 jlist', { maxBuffer: 16 * 1024 * 1024, timeout: 8000 }, (e, out) => {
let pm2 = [];
if (!e) { try {
← cad49df1 auto-data-snapshot: 2026-08-30T17:12:20 (1 data files) — tmp
·
back to Ticket System
·
stage TK-10970 Kamatera fracture-fix memo (gated remote plan 20fa65cc →