MCPcopy
hub / github.com/codeaashu/claude-code / gracefulShutdown

Function gracefulShutdown

src/utils/gracefulShutdown.ts:391–523  ·  view source on GitHub ↗
(
  exitCode = 0,
  reason: ExitReason = 'other',
  options?: {
    getAppState?: () => AppState
    setAppState?: (f: (prev: AppState) => AppState) => void
    /** Printed to stderr after alt-screen exit, before forceExit. */
    finalMessage?: string
  },
)

Source from the content-addressed store, hash-verified

389
390// Graceful shutdown function that drains the event loop
391export async function gracefulShutdown(
392 exitCode = 0,
393 reason: ExitReason = 'other',
394 options?: {
395 getAppState?: () => AppState
396 setAppState?: (f: (prev: AppState) => AppState) => void
397 /** Printed to stderr after alt-screen exit, before forceExit. */
398 finalMessage?: string
399 },
400): Promise<void> {
401 if (shutdownInProgress) {
402 return
403 }
404 shutdownInProgress = true
405
406 // Resolve the SessionEnd hook budget before arming the failsafe so the
407 // failsafe can scale with it. Without this, a user-configured 10s hook
408 // budget is silently truncated by the 5s failsafe (gh-32712 follow-up).
409 const { executeSessionEndHooks, getSessionEndHookTimeoutMs } = await import(
410 './hooks.js'
411 )
412 const sessionEndTimeoutMs = getSessionEndHookTimeoutMs()
413
414 // Failsafe: guarantee process exits even if cleanup hangs (e.g., MCP connections).
415 // Runs cleanupTerminalModes first so a hung cleanup doesn't leave the terminal dirty.
416 // Budget = max(5s, hook budget + 3.5s headroom for cleanup + analytics flush).
417 failsafeTimer = setTimeout(
418 code => {
419 cleanupTerminalModes()
420 printResumeHint()
421 forceExit(code)
422 },
423 Math.max(5000, sessionEndTimeoutMs + 3500),
424 exitCode,
425 )
426 failsafeTimer.unref()
427
428 // Set the exit code that will be used when process naturally exits
429 process.exitCode = exitCode
430
431 // Exit alt screen and print resume hint FIRST, before any async operations.
432 // This ensures the hint is visible even if the process is killed during
433 // cleanup (e.g., SIGKILL during macOS reboot). Without this, the resume
434 // hint would only appear after cleanup functions, hooks, and analytics
435 // flush — which can take several seconds.
436 cleanupTerminalModes()
437 printResumeHint()
438
439 // Flush session data first — this is the most critical cleanup. If the
440 // terminal is dead (SIGHUP, SSH disconnect), hooks and analytics may hang
441 // on I/O to a dead TTY or unreachable network, eating into the
442 // failsafe budget. Session persistence must complete before anything else.
443 let cleanupTimeoutId: ReturnType<typeof setTimeout> | undefined
444 try {
445 const cleanupPromise = (async () => {
446 try {
447 await runCleanupFunctions()
448 } catch {

Callers 15

renderAndRunFunction · 0.85
runFunction · 0.85
handleShutdownApprovalFunction · 0.85
ExitFlowFunction · 0.85
ThemePickerFunction · 0.85
_temp2Function · 0.85
gracefulShutdownSyncFunction · 0.85
callFunction · 0.85
sigintHandlerFunction · 0.85

Calls 13

cleanupTerminalModesFunction · 0.85
printResumeHintFunction · 0.85
forceExitFunction · 0.85
runCleanupFunctionsFunction · 0.85
executeSessionEndHooksFunction · 0.85
profileReportFunction · 0.85
getLastMainRequestIdFunction · 0.85
logEventFunction · 0.85
shutdown1PEventLoggingFunction · 0.85
shutdownDatadogFunction · 0.85
maxMethod · 0.80

Tested by

no test coverage detected