Skip to content

Commit 35d77a7

Browse files
committed
fix: cleanup tui renderer before process.exit()
1 parent c927b4e commit 35d77a7

File tree

3 files changed

+44
-2
lines changed

3 files changed

+44
-2
lines changed

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

Lines changed: 10 additions & 2 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 { cleanupRenderer } from '../utils/renderer-cleanup'
56

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

@@ -64,7 +65,10 @@ export const useExitHandler = ({
6465
exitWarningTimeoutRef.current = null
6566
}
6667

67-
flushAnalytics().then(() => process.exit(0))
68+
flushAnalytics().then(() => {
69+
cleanupRenderer()
70+
process.exit(0)
71+
})
6872
return true
6973
}, [inputValue, setInputValue, nextCtrlCWillExit])
7074

@@ -77,8 +81,12 @@ export const useExitHandler = ({
7781

7882
const flushed = flushAnalytics()
7983
if (flushed && typeof (flushed as Promise<void>).finally === 'function') {
80-
;(flushed as Promise<void>).finally(() => process.exit(0))
84+
;(flushed as Promise<void>).finally(() => {
85+
cleanupRenderer()
86+
process.exit(0)
87+
})
8188
} else {
89+
cleanupRenderer()
8290
process.exit(0)
8391
}
8492
}

cli/src/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import { initializeAgentRegistry } from './utils/local-agent-registry'
2828
import { clearLogFile, logger } from './utils/logger'
2929
import { shouldShowProjectPicker } from './utils/project-picker'
3030
import { saveRecentProject } from './utils/recent-projects'
31+
import { registerRendererForCleanup } from './utils/renderer-cleanup'
3132
import { detectTerminalTheme } from './utils/terminal-color-detection'
3233
import { setOscDetectedTheme } from './utils/theme-system'
3334

@@ -308,6 +309,7 @@ async function main(): Promise<void> {
308309
backgroundColor: 'transparent',
309310
exitOnCtrlC: false,
310311
})
312+
registerRendererForCleanup(renderer)
311313
createRoot(renderer).render(
312314
<QueryClientProvider client={queryClient}>
313315
<AppWithAsyncAuth />

cli/src/utils/renderer-cleanup.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import type { CliRenderer } from '@opentui/core'
2+
3+
/**
4+
* Global reference to the CLI renderer for cleanup on exit.
5+
* This allows the exit handler to properly destroy the renderer,
6+
* which resets terminal state (mouse tracking, focus reporting, raw mode, etc.)
7+
*/
8+
let registeredRenderer: CliRenderer | null = null
9+
10+
/**
11+
* Register the renderer for cleanup on exit.
12+
* Call this after creating the renderer in index.tsx.
13+
*/
14+
export function registerRendererForCleanup(renderer: CliRenderer): void {
15+
registeredRenderer = renderer
16+
}
17+
18+
/**
19+
* Cleanup the renderer by calling destroy().
20+
* This resets terminal state to prevent garbled output after exit.
21+
* Should be called before process.exit() in exit handlers.
22+
*/
23+
export function cleanupRenderer(): void {
24+
if (registeredRenderer && !registeredRenderer.isDestroyed) {
25+
try {
26+
registeredRenderer.destroy()
27+
} catch {
28+
// Ignore errors during cleanup - we're exiting anyway
29+
}
30+
registeredRenderer = null
31+
}
32+
}

0 commit comments

Comments
 (0)