Skip to content

Commit 68c3239

Browse files
committed
Fix agent shutdown hanging on Ctrl+C
1 parent 0c7ee75 commit 68c3239

File tree

2 files changed

+44
-4
lines changed

2 files changed

+44
-4
lines changed

src/agent/run.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { HOST_WORKSPACE_NAME } from '../shared/client-types';
66
import { DEFAULT_AGENT_PORT } from '../shared/constants';
77
import { WorkspaceManager } from '../workspace/manager';
88
import { containerRunning, getContainerName } from '../docker';
9-
import { startEagerImagePull } from '../docker/eager-pull';
9+
import { startEagerImagePull, stopEagerImagePull } from '../docker/eager-pull';
1010
import { TerminalWebSocketServer } from '../terminal/websocket';
1111
import { ChatWebSocketServer } from '../chat/websocket';
1212
import { OpencodeWebSocketServer } from '../chat/opencode-websocket';
@@ -297,17 +297,39 @@ export async function startAgent(options: StartAgentOptions = {}): Promise<void>
297297
startEagerImagePull();
298298
});
299299

300+
let isShuttingDown = false;
301+
300302
const shutdown = async () => {
303+
if (isShuttingDown) {
304+
console.log('[agent] Force exit');
305+
process.exit(0);
306+
}
307+
isShuttingDown = true;
308+
301309
console.log('[agent] Shutting down...');
310+
311+
stopEagerImagePull();
302312
fileWatcher.stop();
313+
303314
if (tailscaleServeActive) {
304315
console.log('[agent] Stopping Tailscale Serve...');
305316
await stopTailscaleServe();
306317
}
318+
307319
chatServer.close();
308320
opencodeServer.close();
309321
terminalServer.close();
322+
323+
server.closeAllConnections();
324+
325+
const forceExitTimeout = setTimeout(() => {
326+
console.log('[agent] Force exit after timeout');
327+
process.exit(0);
328+
}, 3000);
329+
forceExitTimeout.unref();
330+
310331
server.close(() => {
332+
clearTimeout(forceExitTimeout);
311333
console.log('[agent] Server closed');
312334
process.exit(0);
313335
});

src/docker/eager-pull.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const MAX_RETRIES = 10;
77

88
let pullInProgress = false;
99
let pullComplete = false;
10+
let abortController: AbortController | null = null;
1011

1112
async function isDockerAvailable(): Promise<boolean> {
1213
try {
@@ -44,8 +45,15 @@ export async function startEagerImagePull(): Promise<void> {
4445
}
4546

4647
pullInProgress = true;
48+
abortController = new AbortController();
49+
const signal = abortController.signal;
4750

4851
const attemptPull = async (attempt: number): Promise<void> => {
52+
if (signal.aborted) {
53+
pullInProgress = false;
54+
return;
55+
}
56+
4957
if (attempt > MAX_RETRIES) {
5058
console.log('[agent] Max retries reached for image pull - giving up background pull');
5159
pullInProgress = false;
@@ -58,7 +66,8 @@ export async function startEagerImagePull(): Promise<void> {
5866
if (attempt === 1) {
5967
console.log('[agent] Docker not available - will retry in background');
6068
}
61-
setTimeout(() => attemptPull(attempt + 1), RETRY_INTERVAL_MS);
69+
const timer = setTimeout(() => attemptPull(attempt + 1), RETRY_INTERVAL_MS);
70+
timer.unref();
6271
return;
6372
}
6473

@@ -67,14 +76,23 @@ export async function startEagerImagePull(): Promise<void> {
6776
if (success) {
6877
pullComplete = true;
6978
pullInProgress = false;
70-
} else {
71-
setTimeout(() => attemptPull(attempt + 1), RETRY_INTERVAL_MS);
79+
} else if (!signal.aborted) {
80+
const timer = setTimeout(() => attemptPull(attempt + 1), RETRY_INTERVAL_MS);
81+
timer.unref();
7282
}
7383
};
7484

7585
attemptPull(1);
7686
}
7787

88+
export function stopEagerImagePull(): void {
89+
if (abortController) {
90+
abortController.abort();
91+
abortController = null;
92+
}
93+
pullInProgress = false;
94+
}
95+
7896
export function isImagePullComplete(): boolean {
7997
return pullComplete;
8098
}

0 commit comments

Comments
 (0)