Skip to content

Commit 3c74bf2

Browse files
committed
Ensure Ctrl+C exit prints and flushes marker
1 parent f5f8ad5 commit 3c74bf2

File tree

2 files changed

+57
-12
lines changed

2 files changed

+57
-12
lines changed

cli/src/hooks/use-exit-handler.ts

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { useCallback, useEffect, useRef, useState } from 'react'
22

33
import { getCurrentChatId } from '../project-files'
44
import { flushAnalytics } from '../utils/analytics'
5+
import { scheduleGracefulExit } from '../utils/graceful-exit'
56

67
import type { InputValue } from '../state/chat-store'
78

@@ -66,18 +67,7 @@ export const useExitHandler = ({
6667
exitFallbackTimeoutRef.current = null
6768
}
6869

69-
try {
70-
process.stdout.write('\nGoodbye! Exiting...\n')
71-
// Ensure a clear exit marker is rendered for terminal snapshots
72-
process.stdout.write('exit\n')
73-
} catch {
74-
// Ignore stdout write errors during shutdown
75-
}
76-
77-
// Give the terminal a moment to render the exit message before terminating
78-
setTimeout(() => {
79-
process.exit(0)
80-
}, 25)
70+
scheduleGracefulExit()
8171
}, [])
8272

8373
const flushAnalyticsWithTimeout = useCallback(async (timeoutMs = 1000) => {

cli/src/utils/graceful-exit.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
const EXIT_MESSAGE = '\nGoodbye! Exiting...\nexit\n'
2+
3+
let exitStarted = false
4+
5+
function sleep(ms: number): Promise<void> {
6+
return new Promise((resolve) => setTimeout(resolve, ms))
7+
}
8+
9+
async function flushExitMessage(message: string): Promise<void> {
10+
await new Promise<void>((resolve) => {
11+
const handleDrain = () => resolve()
12+
const flushed = process.stdout.write(message, handleDrain)
13+
if (!flushed) {
14+
process.stdout.once('drain', handleDrain)
15+
}
16+
17+
// Always resolve eventually in case stdout is interrupted
18+
setTimeout(resolve, 80)
19+
})
20+
}
21+
22+
/**
23+
* Ensure we print a visible exit marker and give stdout a chance to flush
24+
* before forcing the process to exit.
25+
*/
26+
export async function gracefulExit(options?: {
27+
message?: string
28+
code?: number
29+
}): Promise<void> {
30+
if (exitStarted) return
31+
exitStarted = true
32+
33+
const message = options?.message ?? EXIT_MESSAGE
34+
const code = options?.code ?? 0
35+
36+
try {
37+
await flushExitMessage(message)
38+
// Small delay to let terminal emulators render the exit marker
39+
await sleep(30)
40+
} catch {
41+
// Ignore errors and fall through to exit
42+
}
43+
44+
process.exit(code)
45+
}
46+
47+
/**
48+
* Fire-and-forget exit helper that still flushes stdout before exiting.
49+
*/
50+
export function scheduleGracefulExit(options?: {
51+
message?: string
52+
code?: number
53+
}): void {
54+
void gracefulExit(options)
55+
}

0 commit comments

Comments
 (0)