[object Object]

← 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 →