@link-assistant/hive-mind 1.45.0 → 1.45.1

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,5 +1,14 @@
1
1
  # @link-assistant/hive-mind
2
2
 
3
+ ## 1.45.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 003c5ca: Fix premature finish signaling and leaked child processes (Issue #1516)
8
+ - Kill entire process group on stream timeout using negative PID, preventing leaked /bin/sh child processes from continuing to make commits after completion
9
+ - Move .gitkeep cleanup to after all completion signals (log upload, "Ready to merge" comment) so no new commits appear after the system reports "session ended"
10
+ - drainHandles now reports surviving child processes as errors instead of silently killing them, so root causes are investigated rather than hidden
11
+
3
12
  ## 1.45.0
4
13
 
5
14
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-assistant/hive-mind",
3
- "version": "1.45.0",
3
+ "version": "1.45.1",
4
4
  "description": "AI-powered issue solver and hive mind for collaborative problem solving",
5
5
  "main": "src/hive.mjs",
6
6
  "type": "module",
@@ -852,22 +852,22 @@ export const executeClaudeCommand = async params => {
852
852
  let lastEventTime = null;
853
853
  let activityTimeoutId = null;
854
854
  let isActivityTimeout = false;
855
- // Issue #1510: Separate SIGTERM (graceful) and SIGKILL (force) phases to allow
856
- // capturing final output from the process during graceful shutdown
855
+ // Issue #1516: Kill process group (-pid) so leaked /bin/sh children don't survive
856
+ // prettier-ignore
857
+ const killProcessTree = signal => { try { const pid = execCommand.pid || execCommand._pid; if (pid) { process.kill(-pid, signal); return; } } catch { /* not group leader */ } execCommand.kill(signal); };
857
858
  const forceExitOnTimeout = async () => {
858
859
  if (forceExitTriggered) return;
859
860
  forceExitTriggered = true;
860
- await log(`⚠️ Stream timeout — sending SIGTERM for graceful shutdown (Issue #1280, #1510)`, { verbose: true });
861
+ await log(`⚠️ Stream timeout — sending SIGTERM for graceful shutdown (Issue #1280, #1510, #1516)`, { verbose: true });
861
862
  try {
862
863
  if (execCommand.kill) {
863
- execCommand.kill('SIGTERM');
864
+ killProcessTree('SIGTERM');
864
865
  // Issue #1346/#1510: Follow up with SIGKILL after 5s if still alive
865
- // Increased from 2s to 5s to give more time for final output capture
866
866
  const t = setTimeout(() => {
867
867
  try {
868
868
  if (!execCommand.result?.code) {
869
- log(`⚠️ Process did not exit after SIGTERM, sending SIGKILL`, { verbose: true });
870
- execCommand.kill('SIGKILL');
869
+ log(`⚠️ Process tree did not exit after SIGTERM, sending SIGKILL (Issue #1516)`, { verbose: true });
870
+ killProcessTree('SIGKILL');
871
871
  }
872
872
  } catch {
873
873
  /* exited */
@@ -121,12 +121,30 @@ const drainHandles = async () => {
121
121
  // undici may not be available in all Node versions — safe to ignore
122
122
  }
123
123
 
124
- // 3. Unref surviving child processes from command-stream.
125
- // These are typically already-exited but their OS handle entry lingers.
124
+ // 3. Detect surviving child processes from command-stream.
125
+ // Issue #1516: Surviving ChildProcess handles indicate a bug a leaked /bin/sh
126
+ // child can continue executing (making commits, pushing to GitHub) after we've
127
+ // declared completion. Instead of silently killing them (which hides root causes),
128
+ // we log an error so each occurrence is investigated and the root cause is fixed.
129
+ // The process group kill in claude.lib.mjs (killProcessTree) should have already
130
+ // terminated all children; if any survive, that's a bug we need to know about.
126
131
  try {
127
132
  for (const handle of process._getActiveHandles()) {
128
- if (handle?.constructor?.name === 'ChildProcess' && typeof handle.unref === 'function') {
129
- handle.unref();
133
+ if (handle?.constructor?.name === 'ChildProcess') {
134
+ // Issue #1516: Report surviving child processes as errors instead of killing them.
135
+ // This surfaces the root cause for investigation rather than hiding it.
136
+ const detail = [handle.pid != null ? `pid=${handle.pid}` : null, handle.spawnfile ? `file=${handle.spawnfile}` : null, handle.killed ? 'killed=true' : 'killed=false'].filter(Boolean).join(', ');
137
+ const errorMsg = `❌ ERROR: Surviving ChildProcess detected at exit (${detail}). This indicates a leaked process that was not properly terminated. Investigate the root cause — do NOT suppress this error by killing the process. (Issue #1516)`;
138
+ if (logFunction) {
139
+ await logFunction(errorMsg, { level: 'error' });
140
+ } else {
141
+ console.error(errorMsg);
142
+ }
143
+ // Still unref so Node.js can exit, but do NOT kill — let the OS process
144
+ // continue so its effects are visible and the root cause can be diagnosed.
145
+ if (typeof handle.unref === 'function') {
146
+ handle.unref();
147
+ }
130
148
  }
131
149
  }
132
150
  } catch {
package/src/solve.mjs CHANGED
@@ -1175,8 +1175,7 @@ try {
1175
1175
  const autoRestartEnabled = argv['autoRestartOnUncommittedChanges'] !== false;
1176
1176
  const shouldRestart = await checkForUncommittedChanges(tempDir, owner, repo, branchName, $, log, shouldAutoCommit, autoRestartEnabled);
1177
1177
 
1178
- // Remove initial commit file (CLAUDE.md or .gitkeep) now that Claude command has finished
1179
- await cleanupClaudeFile(tempDir, branchName, claudeCommitHash, argv);
1178
+ // Issue #1516: cleanupClaudeFile() moved to after completion signals (before endWorkSession)
1180
1179
 
1181
1180
  // Show summary of session and log file
1182
1181
  await showSessionSummary(sessionId, limitReached, argv, issueUrl, tempDir, shouldAttachLogs);
@@ -1442,6 +1441,9 @@ try {
1442
1441
  }
1443
1442
  }
1444
1443
 
1444
+ // Issue #1516: Cleanup after all signals (was before verifyResults, caused premature commits)
1445
+ await cleanupClaudeFile(tempDir, branchName, claudeCommitHash, argv);
1446
+
1445
1447
  // End work session using the new module
1446
1448
  await endWorkSession({
1447
1449
  isContinueMode,