Claude-Mem Graceful Shutdown: A Multi-Step Strategy to Prevent Data Loss During Restarts
Claude-Mem implements a coordinated 8-step graceful shutdown sequence in src/services/infrastructure/GracefulShutdown.ts that ensures all HTTP connections close, sessions flush, databases write pending data, and child processes terminate before the process exits with code 0.
Graceful shutdown is critical for persistent AI applications like Claude-Mem to prevent memory loss and data corruption during restarts. The thedotmack/claude-mem repository implements a robust, multi-phase shutdown protocol that systematically releases resources rather than terminating abruptly. This approach ensures that vector store data, session state, and database writes complete successfully even when the service receives termination signals.
Signal Handling Architecture
The shutdown process begins in src/services/worker-service.ts where the WorkerService class registers handlers for SIGTERM, SIGINT, and SIGHUP (on Unix systems). When the operating system sends these signals, the handlers set an internal isShuttingDown flag and invoke the shutdown() method.
This design prevents race conditions by ensuring that only one shutdown path executes, even if multiple signals arrive simultaneously. The signal handlers use a reference object pattern to share state safely between the handler context and the service instance.
Signal Handler Registration
The registerSignalHandlers() method in worker-service.ts (lines 52-78) implements this pattern:
private registerSignalHandlers(): void {
const shutdownRef = { value: this.isShuttingDown };
const handler = createSignalHandler(() => this.shutdown(), shutdownRef);
process.on('SIGTERM', () => {
this.isShuttingDown = shutdownRef.value;
handler('SIGTERM');
});
process.on('SIGINT', () => {
this.isShuttingDown = shutdownRef.value;
handler('SIGINT');
});
// SIGHUP handling omitted for brevity…
}
The 8-Step Shutdown Sequence
The core logic resides in src/services/infrastructure/GracefulShutdown.ts within the performGracefulShutdown() function. This utility executes an ordered sequence that prioritizes data integrity over speed, ensuring that dependent services shut down in the correct order.
Step 1 – Child Process Enumeration
Before releasing resources, the system captures the PIDs of all child processes using getChildProcesses(). This snapshot ensures that even if child processes detach during shutdown, they can still be terminated later.
const childPids = await getChildProcesses(process.pid);
Step 2 – HTTP Server Shutdown
The closeHttpServer() function (lines 111-125) first closes all active connections with server.closeAllConnections(), then shuts down the server itself. A platform-specific delay accommodates Windows socket release behavior, preventing port conflicts on restart.
Steps 3-6 – Service Layer Cleanup
The sequence continues through the application stack:
- Session Manager:
sessionManager.shutdownAll()flushes and terminates all active sessions - MCP Client:
mcpClient.close()signals child processes to exit gracefully - Chroma Server:
chromaServer.stop()halts the local vector store - Database Manager:
dbManager.close()ensures SQLite/ChromaSync writes complete
Step 7 – Force Kill Remaining Children
Any child processes still running after the graceful signals receive a force kill via forceKillProcess(). The system waits up to 5 seconds for confirmation before proceeding.
Step 8 – Final Cleanup
The process removes its PID file and logs completion. All exit paths use code 0 to prevent Windows Terminal from keeping tabs open, as implemented in worker-service.ts lines 923-1085.
Key Implementation Files
| File | Role |
|---|---|
src/services/infrastructure/GracefulShutdown.ts |
Central implementation of the multi-step shutdown sequence |
src/services/worker-service.ts |
Registers OS signals and initiates shutdown |
src/services/infrastructure/ProcessManager.ts |
Helpers for enumerating and terminating child processes |
src/services/infrastructure/HealthMonitor.ts |
Monitors shutdown exit codes for Windows compatibility |
tests/infrastructure/graceful-shutdown.test.ts |
Unit tests verifying each shutdown step |
Summary
Claude-Mem's graceful shutdown approach prevents data loss through systematic resource management:
- Signal-driven initiation:
SIGTERM,SIGINT, andSIGHUPhandlers inworker-service.tstrigger an orderly shutdown rather than abrupt termination - Ordered resource release: The 8-step sequence in
GracefulShutdown.tsprioritizes data integrity by closing HTTP connections, flushing sessions, and writing database changes before terminating child processes - Child process safety: Enumeration and force-kill mechanisms ensure no orphaned processes remain to hold ports or corrupt data files
- Cross-platform compatibility: Windows-specific delays and exit code 0 handling prevent socket conflicts and terminal tab persistence issues
Frequently Asked Questions
What signals trigger Claude-Mem's graceful shutdown?
The system responds to SIGTERM, SIGINT, and SIGHUP (Unix only) signals. These are registered in src/services/worker-service.ts by the registerSignalHandlers() method, which ensures that any termination request initiates the coordinated shutdown sequence rather than killing the process immediately.
How does Claude-Mem ensure database writes complete before exiting?
The shutdown sequence explicitly closes the database manager after flushing sessions but before terminating child processes. In src/services/infrastructure/GracefulShutdown.ts, step 6 calls dbManager.close(), which ensures all pending SQLite and ChromaSync writes are persisted to disk. This ordering prevents data corruption that could occur if the process exited while writes were in flight.
Why does Claude-Mem use exit code 0 instead of error codes during shutdown?
Claude-Mem intentionally exits with code 0 to maintain compatibility with Windows Terminal and other process managers. As implemented in src/services/worker-service.ts (lines 923-1085), using exit code 0 prevents Windows Terminal from keeping tabs open after a graceful stop, while still ensuring all cleanup operations complete. This distinguishes intentional restarts from actual crash conditions.
What happens if a child process refuses to terminate gracefully?
If child processes survive the initial graceful signals, Claude-Mem escalates to force termination. In src/services/infrastructure/GracefulShutdown.ts, step 7 uses forceKillProcess() to send termination signals to any remaining child PIDs enumerated at the start of shutdown. The system waits up to 5 seconds for confirmation, ensuring no orphaned processes remain to hold network ports or file locks.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →