Skip to content

Commit fe1527f

Browse files
danieliserclaude
andcommitted
fix(async): clear all timers to prevent coverage hang
Fixed async resource cleanup preventing vitest coverage from completing: **MCP Aggregator** (aggregator.ts): - Track reconnect timers in MCPConnection interface - Clear reconnect timers in shutdown method - Prevents setTimeout from keeping process alive **QuickJS Sandbox** (quickjs-runtime.ts): - Track recursive check timer in waitForWorker - Clear timeout in executeWithTimeout error path - Prevents recursive setTimeout loop Root cause: Untracked setTimeout/setInterval calls kept Node event loop active after tests completed, preventing clean exit. Impact: Coverage runs should now complete successfully instead of hanging indefinitely after tests finish. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 7309108 commit fe1527f

2 files changed

Lines changed: 21 additions & 2 deletions

File tree

src/mcp/aggregator.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export interface MCPConnection {
1818
lastSeen: Date;
1919
tools: Map<string, ToolInfo>;
2020
retryCount: number;
21+
reconnectTimer?: NodeJS.Timeout;
2122
}
2223

2324
export class MCPAggregator extends EventEmitter {
@@ -396,11 +397,17 @@ export class MCPAggregator extends EventEmitter {
396397

397398
console.log(`⏳ Scheduling reconnect for ${serverName} in ${delay}ms (attempt ${connection.retryCount}/${this.maxRetries})`);
398399

399-
setTimeout(async () => {
400+
// Clear any existing reconnect timer
401+
if (connection.reconnectTimer) {
402+
clearTimeout(connection.reconnectTimer);
403+
}
404+
405+
connection.reconnectTimer = setTimeout(async () => {
400406
if (connection.status === 'error') {
401407
console.log(`🔄 Retrying connection to ${serverName}...`);
402408
await this.connectServer(serverName, connection.config);
403409
}
410+
connection.reconnectTimer = undefined;
404411
}, delay);
405412
}
406413

@@ -439,6 +446,14 @@ export class MCPAggregator extends EventEmitter {
439446
this.healthCheckInterval = null;
440447
}
441448

449+
// Clear all reconnect timers
450+
for (const connection of this.connections.values()) {
451+
if (connection.reconnectTimer) {
452+
clearTimeout(connection.reconnectTimer);
453+
connection.reconnectTimer = undefined;
454+
}
455+
}
456+
442457
// Close all connections
443458
const shutdownPromises = Array.from(this.connections.values()).map(
444459
async (connection) => {

src/sandbox/quickjs-runtime.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,16 +144,19 @@ export class QuickJSSandbox {
144144

145145
private async waitForWorker(): Promise<SandboxWorker> {
146146
return new Promise((resolve, reject) => {
147+
let checkTimer: NodeJS.Timeout | null = null;
147148
const timeout = setTimeout(() => {
149+
if (checkTimer) clearTimeout(checkTimer);
148150
reject(new Error('Timeout waiting for available worker'));
149151
}, 5000);
150152

151153
const checkForWorker = () => {
152154
if (this.workerQueue.length > 0) {
155+
if (checkTimer) clearTimeout(checkTimer);
153156
clearTimeout(timeout);
154157
resolve(this.acquireWorker());
155158
} else {
156-
setTimeout(checkForWorker, 10);
159+
checkTimer = setTimeout(checkForWorker, 10);
157160
}
158161
};
159162

@@ -225,6 +228,7 @@ export class QuickJSSandbox {
225228
if (result.error) {
226229
const error = context.getString(result.error);
227230
result.error.dispose();
231+
clearTimeout(timeoutId);
228232
reject(new Error(error));
229233
} else if ('value' in result) {
230234
const value = result.value;

0 commit comments

Comments
 (0)