Skip to content

Commit 6f92f56

Browse files
committed
fix(dev): prevent orphaned servers and handle empty prompt
Fixes found during bugbash of auto-start feature: - Register process.on('exit') handler to kill auto-started server, ensuring cleanup even when invoke helpers call process.exit(1) - Initialize resolveServerExit with safe default to prevent potential undefined call if Promise executor changes - Add unref() to SIGKILL timer in DevServer.kill() so Node.js exits promptly instead of hanging 2 seconds - Use !== undefined check for prompt so empty string "" enters invoke path instead of falling through to interactive TUI Constraint: process.on('exit') handlers are synchronous; cannot await server shutdown Rejected: Modify invoke helpers to throw instead of process.exit | too many callers to change Confidence: high Scope-risk: narrow Not-tested: rapid consecutive invocations (port TIME_WAIT race)
1 parent 814a1d0 commit 6f92f56

2 files changed

Lines changed: 9 additions & 4 deletions

File tree

src/cli/commands/dev/command.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ export const registerDev = (program: Command) => {
163163

164164
// If a prompt is provided, invoke the dev server (auto-starting if needed)
165165
const invokePrompt = positionalPrompt;
166-
if (invokePrompt) {
166+
if (invokePrompt !== undefined) {
167167
const workingDir = getWorkingDirectory();
168168
const invokeProject = await loadProjectConfig(workingDir);
169169

@@ -211,7 +211,8 @@ export const registerDev = (program: Command) => {
211211
}
212212

213213
const serverErrors: string[] = [];
214-
let resolveServerExit: () => void;
214+
// eslint-disable-next-line @typescript-eslint/no-empty-function
215+
let resolveServerExit: () => void = () => {};
215216
const serverExitPromise = new Promise<void>(resolve => {
216217
resolveServerExit = resolve;
217218
});
@@ -232,6 +233,10 @@ export const registerDev = (program: Command) => {
232233
});
233234
await server.start();
234235

236+
// Ensure server cleanup on any exit path (process.exit in invoke helpers, SIGINT, etc.)
237+
const cleanupServer = () => server.kill();
238+
process.on('exit', cleanupServer);
239+
235240
// Wait for server to accept connections, bail early if process crashes
236241
const ready = await Promise.race([waitForServerReady(invokePort), serverExitPromise.then(() => false)]);
237242

@@ -240,7 +245,6 @@ export const registerDev = (program: Command) => {
240245
console.error(serverErrors.slice(-5).join('\n'));
241246
}
242247
console.error('Error: Dev server failed to start. Run "agentcore dev --logs" for details.');
243-
server.kill();
244248
process.exit(1);
245249
}
246250
autoStartedServer = server;

src/cli/operations/dev/dev-server.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,10 @@ export abstract class DevServer {
8282
kill(): void {
8383
if (!this.child || this.child.killed) return;
8484
this.child.kill('SIGTERM');
85-
setTimeout(() => {
85+
const killTimer = setTimeout(() => {
8686
if (this.child && !this.child.killed) this.child.kill('SIGKILL');
8787
}, 2000);
88+
killTimer.unref();
8889
}
8990

9091
/** Mode-specific setup (e.g., venv creation, container image build). Returns false to abort. */

0 commit comments

Comments
 (0)