Add exit reason types and improve graceful shutdown handling
This commit is contained in:
@@ -441,3 +441,8 @@ export async function connectRemoteControl(
|
||||
): Promise<RemoteControlHandle | null> {
|
||||
throw new Error('not implemented')
|
||||
}
|
||||
|
||||
// add exit reason types for removing the error within gracefulShutdown file
|
||||
export type ExitReason = {
|
||||
|
||||
}
|
||||
@@ -137,7 +137,7 @@ import { generateSessionTitle } from '../utils/sessionTitle.js';
|
||||
import { BASH_INPUT_TAG, COMMAND_MESSAGE_TAG, COMMAND_NAME_TAG, LOCAL_COMMAND_STDOUT_TAG } from '../constants/xml.js';
|
||||
import { escapeXml } from '../utils/xml.js';
|
||||
import type { ThinkingConfig } from '../utils/thinking.js';
|
||||
import { gracefulShutdownSync } from '../utils/gracefulShutdown.js';
|
||||
import { gracefulShutdownSync, isShuttingDown } from '../utils/gracefulShutdown.js';
|
||||
import { handlePromptSubmit, type PromptInputHelpers } from '../utils/handlePromptSubmit.js';
|
||||
import { useQueueProcessor } from '../hooks/useQueueProcessor.js';
|
||||
import { useMailboxBridge } from '../hooks/useMailboxBridge.js';
|
||||
@@ -4886,7 +4886,7 @@ export function REPL({
|
||||
|
||||
{mrRender()}
|
||||
|
||||
{!toolJSX?.shouldHidePromptInput && !focusedInputDialog && !isExiting && !disabled && !cursor && <>
|
||||
{!toolJSX?.shouldHidePromptInput && !focusedInputDialog && !isExiting && !disabled && !cursor && !isShuttingDown() && <>
|
||||
{autoRunIssueReason && <AutoRunIssueNotification onRun={handleAutoRunIssue} onCancel={handleCancelAutoRunIssue} reason={getAutoRunIssueReasonText(autoRunIssueReason)} />}
|
||||
{postCompactSurvey.state !== 'closed' ? <FeedbackSurvey state={postCompactSurvey.state} lastResponse={postCompactSurvey.lastResponse} handleSelect={postCompactSurvey.handleSelect} inputValue={inputValue} setInputValue={setInputValue} onRequestFeedback={handleSurveyRequestFeedback} /> : memorySurvey.state !== 'closed' ? <FeedbackSurvey state={memorySurvey.state} lastResponse={memorySurvey.lastResponse} handleSelect={memorySurvey.handleSelect} handleTranscriptSelect={memorySurvey.handleTranscriptSelect} inputValue={inputValue} setInputValue={setInputValue} onRequestFeedback={handleSurveyRequestFeedback} message="How well did Claude use its memory? (optional)" /> : <FeedbackSurvey state={feedbackSurvey.state} lastResponse={feedbackSurvey.lastResponse} handleSelect={feedbackSurvey.handleSelect} handleTranscriptSelect={feedbackSurvey.handleTranscriptSelect} inputValue={inputValue} setInputValue={setInputValue} onRequestFeedback={didAutoRunIssueRef.current ? undefined : handleSurveyRequestFeedback} />}
|
||||
{/* Frustration-triggered transcript sharing prompt */}
|
||||
|
||||
@@ -56,7 +56,7 @@ import { profileReport } from './startupProfiler.js'
|
||||
* 3. Failing to disable leaves the terminal in a broken state
|
||||
*/
|
||||
/* eslint-disable custom-rules/no-sync-fs -- must be sync to flush before process.exit */
|
||||
function cleanupTerminalModes(): void {
|
||||
function cleanupTerminalModes(skipUnmount: boolean = false): void {
|
||||
if (!process.stdout.isTTY) {
|
||||
return
|
||||
}
|
||||
@@ -84,7 +84,7 @@ function cleanupTerminalModes(): void {
|
||||
// Calling unmount() now does the final render on the alt buffer,
|
||||
// unsubscribes from signal-exit, and writes 1049l exactly once.
|
||||
const inst = instances.get(process.stdout)
|
||||
if (inst?.isAltScreenActive) {
|
||||
if (!skipUnmount && inst?.isAltScreenActive) {
|
||||
try {
|
||||
inst.unmount()
|
||||
} catch {
|
||||
@@ -92,6 +92,11 @@ function cleanupTerminalModes(): void {
|
||||
// so printResumeHint still hits the main buffer.
|
||||
writeSync(1, EXIT_ALT_SCREEN)
|
||||
}
|
||||
} else if (skipUnmount && inst?.isAltScreenActive) {
|
||||
// We already unmounted asynchronously in gracefulShutdown, but if we
|
||||
// fallback to manual alt-screen exit here just in case Ink didn't write it or is dead.
|
||||
// Actually, AlternateScreen unmount writes EXIT_ALT_SCREEN, so if we awaited unmount,
|
||||
// we shouldn't emit it again. So we just do nothing here.
|
||||
}
|
||||
// Catches events that arrived during the unmount tree-walk.
|
||||
// detachForShutdown() below also drains.
|
||||
@@ -411,12 +416,17 @@ export async function gracefulShutdown(
|
||||
)
|
||||
const sessionEndTimeoutMs = getSessionEndHookTimeoutMs()
|
||||
|
||||
// Await one tick so React can flush pending updates from commands (e.g. hiding
|
||||
// the autocomplete menu on /exit) before we detach Ink. This lets log-update
|
||||
// erase floating UI elements from the terminal so they don't linger after exit.
|
||||
await new Promise(r => setTimeout(r, 20))
|
||||
|
||||
// Failsafe: guarantee process exits even if cleanup hangs (e.g., MCP connections).
|
||||
// Runs cleanupTerminalModes first so a hung cleanup doesn't leave the terminal dirty.
|
||||
// Budget = max(5s, hook budget + 3.5s headroom for cleanup + analytics flush).
|
||||
failsafeTimer = setTimeout(
|
||||
code => {
|
||||
cleanupTerminalModes()
|
||||
cleanupTerminalModes(true)
|
||||
printResumeHint()
|
||||
forceExit(code)
|
||||
},
|
||||
@@ -433,7 +443,7 @@ export async function gracefulShutdown(
|
||||
// cleanup (e.g., SIGKILL during macOS reboot). Without this, the resume
|
||||
// hint would only appear after cleanup functions, hooks, and analytics
|
||||
// flush — which can take several seconds.
|
||||
cleanupTerminalModes()
|
||||
cleanupTerminalModes(true)
|
||||
printResumeHint()
|
||||
|
||||
// Flush session data first — this is the most critical cleanup. If the
|
||||
|
||||
Reference in New Issue
Block a user