@yemi33/minions 0.1.614 → 0.1.616

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/CHANGELOG.md CHANGED
@@ -1,11 +1,17 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.614 (2026-04-08)
3
+ ## 0.1.616 (2026-04-08)
4
+
5
+ ### Fixes
6
+ - audit fixes for _killProcessInWorktree
7
+
8
+ ## 0.1.615 (2026-04-08)
4
9
 
5
10
  ### Features
6
11
  - clickable pinned notes and skills with full content + doc-chat
7
12
 
8
13
  ### Fixes
14
+ - only kill orphaned processes before worktree removal, not active ones
9
15
  - kill process holding worktree lock before removal attempt
10
16
  - dedup worktree removal attempts, add Windows rd fallback
11
17
  - accurate LLM call timing and agent metrics on engine page
package/engine/cleanup.js CHANGED
@@ -40,39 +40,52 @@ function worktreeDirMatchesBranch(dirLower, branch) {
40
40
  }
41
41
 
42
42
  /**
43
- * Kill any tracked or PID-filed process whose dispatch ID appears in the worktree dir name.
44
- * Must run BEFORE removeWorktree so file handles are released on Windows.
43
+ * Kill orphaned processes whose dispatch ID appears in the worktree dir name.
44
+ * Only kills processes NOT in the active dispatch queue never kills live agents.
45
45
  */
46
- function _killProcessInWorktree(dir, activeProcesses) {
46
+ function _killProcessInWorktree(dir, activeProcesses, activeDispatchIds) {
47
47
  const dirLower = dir.toLowerCase();
48
- // Check tracked active processes
48
+ const activeIds = activeDispatchIds;
49
+
50
+ // Check tracked in-memory processes — only kill if dispatch is no longer active
49
51
  for (const [id, info] of activeProcesses.entries()) {
50
- if (dirLower.includes(id.toLowerCase().slice(-8))) {
51
- try { shared.killImmediate(info.proc); } catch {}
52
- activeProcesses.delete(id);
53
- }
52
+ if (!dirLower.includes(id.toLowerCase().slice(-8))) continue;
53
+ if (activeIds.has(id)) continue; // still active — do not kill
54
+ try { shared.killImmediate(info.proc); } catch {}
55
+ activeProcesses.delete(id);
56
+ log('info', `Killed orphaned process for dispatch ${id} before worktree removal`);
54
57
  }
55
- // Check PID files in engine/tmp/ — format: pid-{label}-{id}.pid
58
+
59
+ // Check PID files in engine/tmp/ — only kill if no active dispatch matches
56
60
  try {
57
61
  const tmpDir = path.join(ENGINE_DIR, 'tmp');
58
62
  for (const f of fs.readdirSync(tmpDir)) {
59
63
  if (!f.startsWith('pid-') || !f.endsWith('.pid')) continue;
60
- // Extract dispatch ID suffix from filename
61
- const parts = f.replace(/^pid-/, '').replace(/\.pid$/, '').split('-');
62
- const suffix = parts.slice(-1)[0];
63
- if (suffix && dirLower.includes(suffix)) {
64
- const pid = parseInt(fs.readFileSync(path.join(tmpDir, f), 'utf8').trim(), 10);
65
- if (pid > 0) {
66
- try {
67
- if (process.platform === 'win32') {
68
- exec(`taskkill /F /PID ${pid}`, { stdio: 'pipe', timeout: 5000, windowsHide: true });
69
- } else {
70
- process.kill(pid, 'SIGKILL');
71
- }
72
- } catch {} // process may already be dead
73
- }
74
- try { fs.unlinkSync(path.join(tmpDir, f)); } catch {}
64
+ const pidFileName = f.replace(/^pid-/, '').replace(/\.pid$/, '');
65
+ if (!dirLower.includes(pidFileName.slice(-8))) continue;
66
+ // Verify this PID file's dispatch is not active
67
+ const isActive = [...activeIds].some(id => pidFileName.includes(id.slice(-8)));
68
+ if (isActive) continue; // still active — do not kill
69
+ const pid = parseInt(fs.readFileSync(path.join(tmpDir, f), 'utf8').trim(), 10);
70
+ if (pid > 0) {
71
+ // Verify the process is actually a node/claude process before killing
72
+ try {
73
+ if (process.platform === 'win32') {
74
+ const taskInfo = exec(`tasklist /FI "PID eq ${pid}" /NH`, { encoding: 'utf8', timeout: 3000, windowsHide: true });
75
+ if (!taskInfo.toLowerCase().includes('node')) continue; // not a node process — skip
76
+ exec(`taskkill /F /PID ${pid}`, { stdio: 'pipe', timeout: 5000, windowsHide: true });
77
+ } else {
78
+ // Verify it's a node process before killing (prevent recycled PID kill)
79
+ try {
80
+ const psOut = exec(`ps -p ${pid} -o comm=`, { encoding: 'utf8', timeout: 3000 }).trim();
81
+ if (!psOut.includes('node') && !psOut.includes('claude')) continue;
82
+ } catch { continue; } // process dead or ps failed
83
+ process.kill(pid, 'SIGKILL');
84
+ }
85
+ log('info', `Killed orphaned PID ${pid} (${f}) before worktree removal`);
86
+ } catch {} // process may already be dead
75
87
  }
88
+ try { fs.unlinkSync(path.join(tmpDir, f)); } catch {}
76
89
  }
77
90
  } catch {} // tmp dir may not exist
78
91
  }
@@ -191,6 +204,7 @@ function runCleanup(config, verbose = false) {
191
204
 
192
205
  const wtEntries = []; // { dir, wtPath, mtime, shouldClean, isProtected }
193
206
  const dispatch = getDispatch();
207
+ const activeDispatchIds = new Set((dispatch.active || []).map(d => d.id));
194
208
 
195
209
  for (const { dir, wtPath } of allDirs) {
196
210
 
@@ -303,7 +317,7 @@ function runCleanup(config, verbose = false) {
303
317
  if (_attemptedWorktreePaths.has(entry.wtPath)) continue;
304
318
  _attemptedWorktreePaths.add(entry.wtPath);
305
319
  // Kill any process still running in this worktree before removal
306
- _killProcessInWorktree(entry.dir, activeProcesses);
320
+ _killProcessInWorktree(entry.dir, activeProcesses, activeDispatchIds);
307
321
  if (shared.removeWorktree(entry.wtPath, root, worktreeRoot)) {
308
322
  cleaned.worktrees++;
309
323
  if (verbose) console.log(` Removed worktree: ${entry.wtPath}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.614",
3
+ "version": "0.1.616",
4
4
  "description": "Multi-agent AI dev team that runs from ~/.minions/ — five autonomous agents share a single engine, dashboard, and knowledge base",
5
5
  "bin": {
6
6
  "minions": "bin/minions.js"