@link-assistant/hive-mind 1.30.3 ā 1.30.4
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 +16 -0
- package/package.json +1 -1
- package/src/claude.lib.mjs +4 -1
- package/src/exit-handler.lib.mjs +12 -6
- package/src/github-merge.lib.mjs +8 -2
- package/src/hive.mjs +7 -1
- package/src/sentry.lib.mjs +9 -1
- package/src/solve.mjs +8 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 1.30.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 65eefd9: fix: prevent solve command from hanging after PR is merged (Issue #1346)
|
|
8
|
+
|
|
9
|
+
Previously, after the solve command detected a merged PR and printed "PR MERGED! Stopping auto-restart-until-mergeable mode", the process would hang indefinitely instead of exiting.
|
|
10
|
+
|
|
11
|
+
Root cause: The `finally` block in `src/solve.mjs` completed all async work but never called `process.exit(0)`. Active handles on the Node.js event loop (from libraries like `command-stream` and network connections) prevent natural process exit. When Sentry is enabled (`--sentry`), `@sentry/profiling-node` native handles also contribute.
|
|
12
|
+
|
|
13
|
+
The fix:
|
|
14
|
+
1. Restores explicit `š Complete log file:` display in the `finally` block (matching original behavior)
|
|
15
|
+
2. Calls `closeSentry()` from `sentry.lib.mjs` to properly flush Sentry events and release profiling handles when Sentry is enabled (no-op when disabled)
|
|
16
|
+
3. Calls `process.exit(0)` as a required safety net to prevent hanging from any remaining active handles
|
|
17
|
+
4. Adds a hard `Promise.race` timeout (3s) around `sentry.close()` in `exit-handler.lib.mjs` to prevent it from hanging if Sentry's transport stalls
|
|
18
|
+
|
|
3
19
|
## 1.30.3
|
|
4
20
|
|
|
5
21
|
### Patch Changes
|
package/package.json
CHANGED
package/src/claude.lib.mjs
CHANGED
|
@@ -944,7 +944,9 @@ export const executeClaudeCommand = async params => {
|
|
|
944
944
|
if (execCommand.kill) {
|
|
945
945
|
await log(` Sending SIGTERM to process...`, { verbose: true });
|
|
946
946
|
execCommand.kill('SIGTERM');
|
|
947
|
-
|
|
947
|
+
// Issue #1346: Capture timer handle so it can be cleared after use to avoid
|
|
948
|
+
// leaking an active event loop reference when the process exits before 2s
|
|
949
|
+
const sigkillTimerId = setTimeout(() => {
|
|
948
950
|
try {
|
|
949
951
|
if (!execCommand.result?.code) {
|
|
950
952
|
log(` Process still alive after 2s, sending SIGKILL`, { verbose: true });
|
|
@@ -954,6 +956,7 @@ export const executeClaudeCommand = async params => {
|
|
|
954
956
|
/* process may have exited */
|
|
955
957
|
}
|
|
956
958
|
}, 2000);
|
|
959
|
+
sigkillTimerId.unref();
|
|
957
960
|
}
|
|
958
961
|
} catch (e) {
|
|
959
962
|
await log(` Warning: Could not kill process: ${e.message}`, { verbose: true });
|
package/src/exit-handler.lib.mjs
CHANGED
|
@@ -67,11 +67,17 @@ const showExitMessage = async (reason = 'Process exiting', code = 0) => {
|
|
|
67
67
|
export const safeExit = async (code = 0, reason = 'Process completed') => {
|
|
68
68
|
await showExitMessage(reason, code);
|
|
69
69
|
|
|
70
|
-
// Close Sentry to flush any pending events and allow the process to exit cleanly
|
|
70
|
+
// Close Sentry to flush any pending events and allow the process to exit cleanly.
|
|
71
|
+
// Use Promise.race with a hard timeout to guarantee sentry.close() never hangs
|
|
72
|
+
// indefinitely ā the 2000ms hint passed to sentry.close() is forwarded to internal
|
|
73
|
+
// flush logic, but the outer Promise itself has no built-in deadline, so we enforce one.
|
|
71
74
|
try {
|
|
72
75
|
const sentry = await getSentry();
|
|
73
76
|
if (sentry && sentry.close) {
|
|
74
|
-
await
|
|
77
|
+
await Promise.race([
|
|
78
|
+
sentry.close(2000),
|
|
79
|
+
new Promise(resolve => setTimeout(resolve, 3000)), // hard 3s deadline
|
|
80
|
+
]);
|
|
75
81
|
}
|
|
76
82
|
} catch {
|
|
77
83
|
// Ignore Sentry.close() errors - exit anyway
|
|
@@ -119,7 +125,7 @@ export const installGlobalExitHandlers = () => {
|
|
|
119
125
|
try {
|
|
120
126
|
const sentry = await getSentry();
|
|
121
127
|
if (sentry && sentry.close) {
|
|
122
|
-
await sentry.close(2000);
|
|
128
|
+
await Promise.race([sentry.close(2000), new Promise(resolve => setTimeout(resolve, 3000))]);
|
|
123
129
|
}
|
|
124
130
|
} catch {
|
|
125
131
|
// Ignore Sentry.close() errors
|
|
@@ -140,7 +146,7 @@ export const installGlobalExitHandlers = () => {
|
|
|
140
146
|
try {
|
|
141
147
|
const sentry = await getSentry();
|
|
142
148
|
if (sentry && sentry.close) {
|
|
143
|
-
await sentry.close(2000);
|
|
149
|
+
await Promise.race([sentry.close(2000), new Promise(resolve => setTimeout(resolve, 3000))]);
|
|
144
150
|
}
|
|
145
151
|
} catch {
|
|
146
152
|
// Ignore Sentry.close() errors
|
|
@@ -164,7 +170,7 @@ export const installGlobalExitHandlers = () => {
|
|
|
164
170
|
try {
|
|
165
171
|
const sentry = await getSentry();
|
|
166
172
|
if (sentry && sentry.close) {
|
|
167
|
-
await sentry.close(2000);
|
|
173
|
+
await Promise.race([sentry.close(2000), new Promise(resolve => setTimeout(resolve, 3000))]);
|
|
168
174
|
}
|
|
169
175
|
} catch {
|
|
170
176
|
// Ignore Sentry.close() errors
|
|
@@ -188,7 +194,7 @@ export const installGlobalExitHandlers = () => {
|
|
|
188
194
|
try {
|
|
189
195
|
const sentry = await getSentry();
|
|
190
196
|
if (sentry && sentry.close) {
|
|
191
|
-
await sentry.close(2000);
|
|
197
|
+
await Promise.race([sentry.close(2000), new Promise(resolve => setTimeout(resolve, 3000))]);
|
|
192
198
|
}
|
|
193
199
|
} catch {
|
|
194
200
|
// Ignore Sentry.close() errors
|
package/src/github-merge.lib.mjs
CHANGED
|
@@ -769,9 +769,15 @@ export async function waitForCI(owner, repo, prNumber, options = {}, verbose = f
|
|
|
769
769
|
}
|
|
770
770
|
|
|
771
771
|
if (onStatusUpdate) {
|
|
772
|
-
// Issue #1269: Wrap callback with timeout to prevent infinite blocking
|
|
772
|
+
// Issue #1269: Wrap callback with timeout to prevent infinite blocking; #1346: capture and clear timeout handle to prevent dangling timer
|
|
773
773
|
try {
|
|
774
|
-
|
|
774
|
+
let callbackTimeoutId;
|
|
775
|
+
await Promise.race([
|
|
776
|
+
onStatusUpdate(ciStatus),
|
|
777
|
+
new Promise((_, reject) => {
|
|
778
|
+
callbackTimeoutId = setTimeout(() => reject(new Error(`Callback timeout after ${callbackTimeout}ms`)), callbackTimeout);
|
|
779
|
+
}),
|
|
780
|
+
]).finally(() => clearTimeout(callbackTimeoutId));
|
|
775
781
|
} catch (callbackError) {
|
|
776
782
|
// Issue #1269: Log callback errors but continue processing
|
|
777
783
|
console.error(`[ERROR] /merge: Status update callback failed for PR #${prNumber}: ${callbackError.message}`);
|
package/src/hive.mjs
CHANGED
|
@@ -52,7 +52,13 @@ if (isDirectExecution) {
|
|
|
52
52
|
console.log(' Loading dependencies (this may take a moment)...');
|
|
53
53
|
// Helper function to add timeout to async operations
|
|
54
54
|
const withTimeout = (promise, timeoutMs, operation) => {
|
|
55
|
-
|
|
55
|
+
let timeoutId;
|
|
56
|
+
return Promise.race([
|
|
57
|
+
promise,
|
|
58
|
+
new Promise((_, reject) => {
|
|
59
|
+
timeoutId = setTimeout(() => reject(new Error(`Operation '${operation}' timed out after ${timeoutMs}ms. This might be due to slow network or npm configuration issues.`)), timeoutMs);
|
|
60
|
+
}),
|
|
61
|
+
]).finally(() => clearTimeout(timeoutId));
|
|
56
62
|
};
|
|
57
63
|
// Use use-m to dynamically import modules for cross-runtime compatibility
|
|
58
64
|
if (typeof use === 'undefined') {
|
package/src/sentry.lib.mjs
CHANGED
|
@@ -273,8 +273,16 @@ export const closeSentry = async (timeout = 2000) => {
|
|
|
273
273
|
return;
|
|
274
274
|
}
|
|
275
275
|
|
|
276
|
+
// Issue #1346: Use Promise.race with a hard deadline so a hung sentry.close()
|
|
277
|
+
// (e.g. from @sentry/profiling-node native thread) cannot block the caller forever.
|
|
276
278
|
try {
|
|
277
|
-
|
|
279
|
+
let hardDeadlineId;
|
|
280
|
+
await Promise.race([
|
|
281
|
+
sentry.close(timeout),
|
|
282
|
+
new Promise(resolve => {
|
|
283
|
+
hardDeadlineId = setTimeout(resolve, timeout + 1000);
|
|
284
|
+
}),
|
|
285
|
+
]).finally(() => clearTimeout(hardDeadlineId));
|
|
278
286
|
} catch (error) {
|
|
279
287
|
// Silently fail if close fails
|
|
280
288
|
if (process.env.DEBUG === 'true') {
|
package/src/solve.mjs
CHANGED
|
@@ -41,7 +41,7 @@ const config = await import('./solve.config.lib.mjs');
|
|
|
41
41
|
const { initializeConfig, parseArguments } = config;
|
|
42
42
|
// Import Sentry integration
|
|
43
43
|
const sentryLib = await import('./sentry.lib.mjs');
|
|
44
|
-
const { initializeSentry, addBreadcrumb, reportError } = sentryLib;
|
|
44
|
+
const { initializeSentry, addBreadcrumb, reportError, closeSentry } = sentryLib;
|
|
45
45
|
const { yargs, hideBin } = await initializeConfig(use);
|
|
46
46
|
const path = (await use('path')).default;
|
|
47
47
|
const fs = (await use('fs')).promises;
|
|
@@ -1485,14 +1485,15 @@ try {
|
|
|
1485
1485
|
$,
|
|
1486
1486
|
});
|
|
1487
1487
|
} finally {
|
|
1488
|
-
// Clean up temporary directory using repository module
|
|
1489
1488
|
await cleanupTempDirectory(tempDir, argv, limitReached);
|
|
1490
1489
|
|
|
1491
|
-
// Show final log file reference
|
|
1492
|
-
// This ensures users always know where to find the complete log file
|
|
1490
|
+
// Show final log file reference so users always know where to find the complete log
|
|
1493
1491
|
if (getLogFile()) {
|
|
1494
|
-
const
|
|
1495
|
-
|
|
1496
|
-
await log(`\nš Complete log file: ${absoluteLogPath}`);
|
|
1492
|
+
const finalLogPath = path.resolve(getLogFile());
|
|
1493
|
+
await log(`\nš Complete log file: ${finalLogPath}`);
|
|
1497
1494
|
}
|
|
1495
|
+
|
|
1496
|
+
// Issue #1346: Flush Sentry events before exit.
|
|
1497
|
+
// closeSentry() uses a hard Promise.race deadline so it cannot block indefinitely.
|
|
1498
|
+
await closeSentry();
|
|
1498
1499
|
}
|