@axiomatic-labs/claudeflow 2.13.72 → 2.13.73

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/doctor.js CHANGED
@@ -251,6 +251,46 @@ function checkObserverState(cwd) {
251
251
  };
252
252
  }
253
253
 
254
+ // Detects more than one process listening on the observer port. macOS allows
255
+ // distinct sockets on the same port for IPv4 and IPv6, so a stale daemon
256
+ // bound to "localhost" (which old code resolved to ::1) could coexist with a
257
+ // fresh daemon bound to 127.0.0.1. Snapshots routed via `localhost` then
258
+ // landed on the stale daemon, which carried the pre-v2.13.71 auto-resolve
259
+ // bug — panel saw the new daemon's empty state, hook saw 1 incident. This
260
+ // check surfaces that condition explicitly.
261
+ function checkObserverDualBind(cwd) {
262
+ const observer = readObserverState(cwd);
263
+ if (!observer.port) {
264
+ return { id: 'observer-dual-bind', severity: 'info', message: 'No observer port known — skipping dual-bind check.' };
265
+ }
266
+ const port = observer.port;
267
+ let listeners = [];
268
+ try {
269
+ const { spawnSync } = require('child_process');
270
+ const r = spawnSync('lsof', [`-iTCP:${port}`, '-sTCP:LISTEN', '-P', '-n', '-F', 'pn'], { encoding: 'utf8', timeout: 1500 });
271
+ if (r.status === 0 && r.stdout) {
272
+ let currentPid = null;
273
+ for (const line of r.stdout.split('\n')) {
274
+ if (line.startsWith('p')) currentPid = Number(line.slice(1));
275
+ else if (line.startsWith('n') && currentPid) listeners.push({ pid: currentPid, name: line.slice(1) });
276
+ }
277
+ }
278
+ } catch {
279
+ return { id: 'observer-dual-bind', severity: 'info', message: 'lsof unavailable — cannot verify single-bind on observer port.' };
280
+ }
281
+ const uniquePids = new Set(listeners.map((l) => l.pid));
282
+ if (uniquePids.size <= 1) {
283
+ return { id: 'observer-dual-bind', severity: 'ok', message: `Observer port ${port} has a single listener.` };
284
+ }
285
+ return {
286
+ id: 'observer-dual-bind',
287
+ severity: 'error',
288
+ message: `Observer port ${port} has ${uniquePids.size} distinct processes listening (PIDs: ${[...uniquePids].join(', ')}). One is likely a stale daemon from a previous version. Browser snapshots may route to the wrong instance, causing the panel and the Stop hook to disagree. Fix: stop the older PID (\`kill <pid>\`) and let the SessionStart hook respawn a single daemon.`,
289
+ detail: listeners.map((l) => ({ file: `pid ${l.pid}`, reason: l.name })),
290
+ fix: { kind: 'dual-bind', listeners },
291
+ };
292
+ }
293
+
254
294
  function applyStaleLockfileFix(check) {
255
295
  for (const { file } of check.fix.stale) {
256
296
  try { fs.unlinkSync(file); } catch {}
@@ -286,6 +326,7 @@ async function run(argv = []) {
286
326
  checkCdpPortMismatch(cwd),
287
327
  checkStaleLockfiles(cwd),
288
328
  checkObserverState(cwd),
329
+ checkObserverDualBind(cwd),
289
330
  ];
290
331
 
291
332
  for (const check of checks) printCheck(check);
@@ -326,6 +367,7 @@ module.exports.deriveCdpPort = deriveCdpPort;
326
367
  module.exports.checkCdpPortMismatch = checkCdpPortMismatch;
327
368
  module.exports.checkStaleLockfiles = checkStaleLockfiles;
328
369
  module.exports.checkObserverState = checkObserverState;
370
+ module.exports.checkObserverDualBind = checkObserverDualBind;
329
371
  module.exports.readObserverState = readObserverState;
330
372
  module.exports.readPlaywrightCdpEndpoint = readPlaywrightCdpEndpoint;
331
373
  module.exports.applyCdpPortFix = applyCdpPortFix;
package/lib/install.js CHANGED
@@ -117,7 +117,7 @@ async function run() {
117
117
  command: "npx",
118
118
  args: [
119
119
  "@playwright/mcp@latest",
120
- "--cdp-endpoint", `http://localhost:${cdpPort}`,
120
+ "--cdp-endpoint", `http://127.0.0.1:${cdpPort}`,
121
121
  "--caps", "vision,devtools",
122
122
  ],
123
123
  };
@@ -220,6 +220,12 @@ async function run() {
220
220
  fs.rmSync(tmpExtract, { recursive: true, force: true });
221
221
  }
222
222
 
223
+ // Kill any running observer daemon so the next request spawns a fresh one
224
+ // with the updated runtime code. Pre-fix: a running daemon held the old
225
+ // version in memory until manually killed, causing the bridge to talk to
226
+ // stale logic (see v2.13.71→v2.13.72 dual-daemon incident).
227
+ killStaleObserverDaemon(cwd);
228
+
223
229
  // Install analysis tools (ast-grep + Serena MCP)
224
230
  installAnalysisTools();
225
231
 
@@ -282,6 +288,33 @@ async function run() {
282
288
  showGettingStarted(cwd, cliStatus);
283
289
  }
284
290
 
291
+ function killStaleObserverDaemon(cwd) {
292
+ const pidPath = path.join(cwd, '.claudeflow', 'tmp', 'error-observer.pid');
293
+ if (!fs.existsSync(pidPath)) return;
294
+ let pid;
295
+ try {
296
+ pid = Number(fs.readFileSync(pidPath, 'utf8').trim());
297
+ } catch {
298
+ return;
299
+ }
300
+ if (!Number.isFinite(pid) || pid <= 1) return;
301
+ try {
302
+ // signal 0 = "is this PID alive and ours to signal?"
303
+ process.kill(pid, 0);
304
+ } catch {
305
+ // Not running or not ours. Clean up the stale pidfile and move on.
306
+ try { fs.unlinkSync(pidPath); } catch {}
307
+ return;
308
+ }
309
+ try {
310
+ process.kill(pid, 'SIGTERM');
311
+ ui.success(`Stopped running observer daemon (pid ${pid}) so the update takes effect`);
312
+ } catch {
313
+ // Swallow — non-fatal. The user can restart manually if needed.
314
+ }
315
+ try { fs.unlinkSync(pidPath); } catch {}
316
+ }
317
+
285
318
  function ensureGlobalCli(version) {
286
319
  // Returns { available: boolean, autoInstalled: boolean, upgraded: boolean, fromVersion?: string, error?: string }
287
320
  // `commandExists('claudeflow')` yields a false positive when install.js runs
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axiomatic-labs/claudeflow",
3
- "version": "2.13.72",
3
+ "version": "2.13.73",
4
4
  "description": "Claudeflow — AI-powered development toolkit for Claude Code. Skills, agents, hooks, and quality gates that ship production apps.",
5
5
  "bin": {
6
6
  "claudeflow": "./bin/cli.js"