# Parent-Process Watchdog in Codebase-Memory-MCP: Preventing Orphaned Server Processes

> Discover the parent-process watchdog in Codebase-Memory-MCP. This feature prevents orphaned server processes by monitoring the parent PID and triggering shutdown, ensuring clean job completion.

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

---

**The parent-process watchdog is a lightweight background thread that monitors the server's parent process ID via `getppid()` and triggers immediate shutdown when the supervising process terminates, ensuring no orphaned MCP server processes persist after job completion.**

The **parent-process watchdog** is a critical reliability mechanism in the `DeusData/codebase-memory-mcp` repository. It solves the common distributed systems problem where server processes survive as zombies after their launching shell or supervisor dies. By continuously polling the parent PID against the original value captured at startup, the watchdog guarantees that the MCP server exits cleanly alongside its controlling process.

## What Is the Parent-Process Watchdog?

The watchdog is a **dedicated monitoring thread** spawned immediately when the MCP server starts. Unlike signal-based approaches that can miss rapid parent replacements, this implementation uses active polling to detect when the original parent process disappears or is replaced by the init system (PID 1).

### Core Architecture

According to the source code in [`src/main.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/main.c), the watchdog thread is created early in the server lifecycle to catch parent death even during initialization. The implementation uses a minimal stack footprint to avoid resource bloat:

- **Stack size**: `PARENT_WATCHDOG_STACK_SIZE` set to `64 * CBM_SZ_1K` (64 KB)
- **Polling interval**: 100 milliseconds (`usleep(100 * 1000)`)
- **Shutdown flag**: Atomic variable `g_shutdown` synchronized across threads

## How the Watchdog Prevents Orphaned Processes

The mechanism relies on POSIX process semantics where orphaned children are re-parented to the init process. The watchdog detects this transition and forces termination before the server becomes a resource-consuming zombie.

### Detecting Parent Death via getppid()

In [`src/watcher/watcher.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/watcher/watcher.c), the `parent_watchdog_thread()` function implements the monitoring loop. It compares the current parent PID against the `original_ppid` captured at startup:

```c
/* src/watcher/watcher.c – parent monitoring logic */
static void *parent_watchdog_thread(void *arg) {
    pid_t original_ppid = *(pid_t *)arg;
    while (atomic_load(&g_shutdown) == 0) {
        if (getppid() != original_ppid) {
            /* Parent has died or been replaced by init */
            atomic_store(&g_shutdown, 1);
            break;
        }
        usleep(100 * 1000);   /* 100 ms polling interval */
    }
    return NULL;
}

```

When `getppid()` returns `1` (init) or any value different from `original_ppid`, the watchdog knows the supervising process has terminated.

### The Shutdown Signal Flow

Once parent death is detected, the watchdog executes a deterministic cleanup sequence:

1. **Sets global flag**: `atomic_store(&g_shutdown, 1)` signals all worker threads to terminate
2. **Breaks polling loop**: The watchdog thread exits its monitoring cycle
3. **Main thread joins**: In [`src/main.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/main.c), the main thread calls `cbm_thread_join(&parent_watchdog_tid)` to ensure synchronous cleanup
4. **Resource release**: The server closes file descriptors and exits before becoming an orphan

### Worker-Mode Safety

The same watchdog mechanism protects **worker-mode subprocesses** used for indexing operations. As tested in [`tests/test_worker_watchdog.sh`](https://github.com/DeusData/codebase-memory-mcp/blob/main/tests/test_worker_watchdog.sh), child worker processes monitor their immediate parent. If the parent worker dies, the child terminates immediately rather than continuing to process jobs that will never be collected.

## Implementation in the Codebase-Memory-MCP Source

The watchdog spans three primary files with distinct responsibilities:

- **[`src/main.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/main.c)**: Creates the watchdog thread via `cbm_thread_create()` and stores the initial PPID before any forking occurs
- **[`src/watcher/watcher.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/watcher/watcher.c)**: Contains the `parent_watchdog_thread()` implementation with the polling logic
- **[`tests/test_parent_watchdog.sh`](https://github.com/DeusData/codebase-memory-mcp/blob/main/tests/test_parent_watchdog.sh)**: Regression test that verifies the server exits within seconds of parent process termination

Thread creation in [`src/main.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/main.c) follows this pattern:

```c
/* src/main.c – watchdog startup sequence */
bool parent_watchdog_started = false;
cbm_thread_t parent_watchdog_tid;
pid_t initial_ppid = getppid();  /* Capture before thread starts */

if (cbm_thread_create(&parent_watchdog_tid,
                      PARENT_WATCHDOG_STACK_SIZE,
                      parent_watchdog_thread,
                      &initial_ppid) == 0) {
    parent_watchdog_started = true;
    cbm_log_info("parent.watchdog.start");
}

/* Later during shutdown... */
if (parent_watchdog_started) {
    cbm_thread_join(&parent_watchdog_tid);
}

```

## Summary

- The **parent-process watchdog** is a 64 KB stack thread that polls `getppid()` every 100 milliseconds to detect supervisor death.
- It prevents zombie processes by setting the atomic `g_shutdown` flag when the parent PID changes or becomes init (PID 1).
- The implementation in [`src/watcher/watcher.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/watcher/watcher.c) uses POSIX-compliant process monitoring rather than fragile signal handling.
- **Worker-mode processes** reuse the same watchdog logic, ensuring distributed indexing tasks cannot outlive their managing parent.
- The main thread in [`src/main.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/main.c) synchronously joins the watchdog during cleanup, guaranteeing no detached threads persist after server exit.

## Frequently Asked Questions

### What happens if the parent process crashes suddenly?

The watchdog detects the crash within 100 milliseconds when `getppid()` returns a different value (typically PID 1 if the parent died). It immediately sets `g_shutdown` to 1, triggering the server's graceful termination sequence before the process becomes orphaned.

### Does the watchdog consume significant CPU or memory?

No. The watchdog thread uses a fixed 64 KB stack (`PARENT_WATCHDOG_STACK_SIZE`) and sleeps for 100 ms between checks. This polling strategy consumes negligible CPU compared to the I/O-bound MCP server operations, and the memory footprint is smaller than a single file buffer.

### Can the watchdog be disabled or configured?

According to the source in [`src/main.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/main.c), watchdog startup is conditional on successful thread creation, but there is no configuration flag to disable it. Failure to create the thread logs a warning (`parent.watchdog.unavailable`) but allows the server to continue running without protection.

### How does this differ from traditional SIGCHLD handling?

Unlike `SIGCHLD`, which only notifies child processes of parent state changes (and cannot be received by the child about its own parent), the watchdog actively queries the parent relationship via `getppid()`. This approach works even when the parent is killed with `SIGKILL`, which cannot be caught or handled by signal handlers in the parent process.