# How codebase-memory-mcp Handles Graceful Shutdown with POSIX Signals

> Learn how codebase-memory-mcp gracefully shuts down with POSIX signals. Discover the five-step cleanup sequence protecting your knowledge graph and preventing resource leaks.

- Repository: [Martin Vogel/codebase-memory-mcp](https://github.com/DeusData/codebase-memory-mcp)
- Tags: internals
- Published: 2026-07-17

---

**The codebase-memory-mcp server registers POSIX signal handlers that capture termination signals in an atomic variable, triggering a five-step cleanup sequence—closing the listener socket, flushing SQLite transactions, stopping background workers, writing optional diagnostics, and classifying the exit status—to ensure the knowledge graph remains uncorrupted and no resources leak.**

The `codebase-memory-mcp` repository from DeusData implements a long-running MCP server in C that must maintain knowledge-graph integrity across process restarts. Its graceful shutdown architecture separates asynchronous signal capture from synchronous cleanup logic, ensuring that user-initiated `Ctrl-C` or system `kill` commands safely commit in-flight transactions and release all allocated resources.

## Signal Registration and Atomic Capture

In [`src/main.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/main.c) (lines approximately 210–225), the **main** entry point installs handlers for standard termination signals during server initialization. The implementation uses a `volatile sig_atomic_t` variable named `g_terminate_signal` to ensure thread-safe signal capture without race conditions.

```c
static volatile sig_atomic_t g_terminate_signal = 0;

static void handle_signal(int sig) {
    g_terminate_signal = sig;          // store the signal that arrived
}

void install_signal_handlers(void) {
    struct sigaction sa = { .sa_handler = handle_signal };
    sigemptyset(&sa.sa_mask);
    sigaction(SIGINT,  &sa, NULL);
    sigaction(SIGTERM, &sa, NULL);
    sigaction(SIGQUIT, &sa, NULL);
    sigaction(SIGALRM, &sa, NULL);
}

```

The **main event loop** (lines approximately 250–275) polls the atomic flag on each iteration. When `g_terminate_signal` becomes non-zero, the loop exits and proceeds to the cleanup phase.

```c
while (!g_terminate_signal) {
    cbm_server_poll_once();   // process one RPC request if any
}

```

## The Five-Step Graceful Shutdown Sequence

Once a signal is detected, the server executes a deterministic cleanup procedure defined in [`src/main.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/main.c) (lines approximately 280–320). This sequence ensures that the embedded SQLite store and background threads terminate cleanly.

1. **Stop accepting new RPC requests** – `cbm_server_stop()` closes the listener socket immediately, preventing new connections from entering the system.
2. **Flush in-memory indexes** – `cbm_store_close()` (implemented in [`src/store.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/store.c)) commits the current SQLite transaction and checkpoints the Write-Ahead Log (WAL), guaranteeing that the knowledge graph persists to disk.
3. **Stop background workers** – `cbm_watcher_stop()` (implemented in [`src/watcher.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/watcher.c)) signals the index-watcher, auto-watcher, and file-watcher threads to terminate, while `cbm_workers_join()` waits for all parallel indexing workers to finish.
4. **Write diagnostics** – If the `CBM_DIAGNOSTICS` environment variable is enabled, `cbm_diagnostics_write_final()` writes a final snapshot to `/tmp/cbm-diagnostics-<pid>.json`.
5. **Exit with classification** – The process returns a status determined by `cbm_proc_classify()`, mapping the received signal to a semantic exit category.

```c
if (g_terminate_signal) {
    cbm_log_info("Shutdown signal %d received, cleaning up …", g_terminate_signal);
    cbm_server_stop();                // close listener socket
    cbm_store_close();                // commit & checkpoint SQLite DB
    cbm_watcher_stop();               // stop background file-watcher
    cbm_workers_join();               // wait for all worker threads
    cbm_diagnostics_write_final();    // optional diagnostics dump
}

```

## Process Exit Classification

The helper function `cbm_proc_classify()` in [`src/proc.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/proc.c) (lines approximately 430–460) translates POSIX signal numbers into semantic process states. This classification allows external orchestrators to distinguish between intentional restarts and fatal errors.

```c
enum cbm_proc_status cbm_proc_classify(bool is_child, int pid,
                                        int sig, bool is_hang) {
    switch (sig) {
        case SIGSEGV: case SIGABRT: case SIGBUS:
            return CBM_PROC_CRASH;
        case SIGTERM: case SIGINT: case SIGQUIT:
            return is_hang ? CBM_PROC_HANG : CBM_PROC_KILLED;
        default:
            return CBM_PROC_UNKNOWN;
    }
}

```

**Signal-to-state mappings:**

- **SIGTERM**, **SIGINT**, **SIGQUIT** → `CBM_PROC_KILLED` (clean shutdown)
- **SIGSEGV**, **SIGABRT**, **SIGBUS** → `CBM_PROC_CRASH` (abnormal exit)
- **SIGKILL** (or forced termination) → `CBM_PROC_HANG` (no cleanup possible)

## Practical Shutdown Examples

To trigger a graceful shutdown manually, send `SIGINT` (equivalent to pressing `Ctrl-C`) or `SIGTERM` to the process:

```bash
$ codebase-memory-mcp --port=9749 &
[1] 12345

# … server is running …

$ kill -INT 12345          # equivalent to Ctrl-C

```

The server logs the signal, executes the five-step cleanup, and exits with status `0`.

To capture a final diagnostics snapshot during shutdown, enable the diagnostics flag before starting:

```bash
export CBM_DIAGNOSTICS=1
codebase-memory-mcp &

# … send SIGTERM …

# After exit you’ll find /tmp/cbm-diagnostics-<pid>.json

```

## Summary

- **Signal capture**: The server uses `sigaction()` in [`src/main.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/main.c) to register handlers for `SIGINT`, `SIGTERM`, `SIGQUIT`, and `SIGALRM`, storing the signal number in the atomic variable `g_terminate_signal`.
- **Cleanup sequence**: The main loop exits when the flag is set, then closes sockets via `cbm_server_stop()`, flushes SQLite via `cbm_store_close()`, stops watchers via `cbm_watcher_stop()`, joins worker threads, and optionally writes diagnostics.
- **Data integrity**: By separating signal reception from the shutdown logic, the server ensures the SQLite WAL is checkpointed and the knowledge graph is never left in a corrupted state.
- **Exit classification**: The `cbm_proc_classify()` function in [`src/proc.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/proc.c) maps termination signals to `CBM_PROC_KILLED`, `CBM_PROC_CRASH`, or `CBM_PROC_HANG` for observability.

## Frequently Asked Questions

### What signals trigger a graceful shutdown in codebase-memory-mcp?

The server registers handlers for `SIGINT`, `SIGTERM`, `SIGQUIT`, and `SIGALRM` in [`src/main.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/main.c). When any of these signals are received, the atomic variable `g_terminate_signal` is set, causing the main event loop to exit and initiate the five-step cleanup sequence. Signals like `SIGKILL` bypass this mechanism and result in immediate termination without cleanup.

### How does the server prevent SQLite corruption during shutdown?

The `cbm_store_close()` function in [`src/store.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/store.c) commits any pending transactions and checkpoints the Write-Ahead Log (WAL) before the process exits. This ensures that all in-memory graph updates are persisted to disk atomically, preventing partial writes or database corruption even if the shutdown occurs during heavy indexing operations.

### What is the difference between CBM_PROC_KILLED and CBM_PROC_CRASH?

According to the classification logic in [`src/proc.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/proc.c), `CBM_PROC_KILLED` indicates that the process terminated via `SIGTERM`, `SIGINT`, or `SIGQUIT` with full cleanup performed. `CBM_PROC_CRASH` indicates an abnormal termination caused by `SIGSEGV`, `SIGABRT`, or `SIGBUS`, where the process may not have completed the graceful shutdown sequence.

### Can I trigger a shutdown programmatically without sending OS signals?

Yes. The MCP server exposes a built-in `shutdown` tool that can be invoked by an authorized client. This tool internally sends `SIGTERM` to the server process, triggering the same atomic flag and cleanup sequence as an external signal, ensuring consistent behavior whether shutdown is initiated via RPC or system signals.