Parent-Process Watchdog in Codebase-Memory-MCP: Purpose and Implementation

The parent-process watchdog is a lightweight background thread that monitors the process's parent PID and triggers atomic shutdown when the original parent dies, preventing orphaned MCP servers and resource leaks.

Codebase-Memory-MCP operates as a long-running daemon that indexes codebases and responds to Model Context Protocol (MCP) requests. Because it can be launched from terminals, supervisor scripts, or container environments, the server must handle abrupt parent termination without leaving zombie processes or leaking SQLite locks. The parent-process watchdog implemented in src/main.c solves this by continuously checking if the launching process still exists.

Why Codebase-Memory-MCP Needs a Parent-Process Watchdog

When Codebase-Memory-MCP starts via command line, as a background worker (--index-worker), or from a container orchestrator, it initially binds to the parent process that spawned it. If that parent crashes, closes, or gets killed, the MCP process becomes an orphaned process. Without intervention, this leads to:

  • Resource leakage: Open SQLite database connections remain locked, temporary files persist, and network sockets stay bound.
  • Wasted compute: Index workers continue consuming CPU and memory indexing codebases that no client will query.
  • Unclean state: Subsequent restarts may encounter file locks or corrupted index states from the orphaned instance.

The parent-process watchdog ensures that when the parent disappears, the MCP server detects the change immediately and initiates a graceful shutdown sequence.

How the Watchdog Detects Parent Death

The watchdog implementation relies on POSIX process management and atomic memory operations to safely signal termination across threads.

The Polling Mechanism

At startup in src/main.c, the watchdog stores the initial parent PID using getppid() and launches a detached thread that polls this value every 250 milliseconds. The thread compares the current parent PID against the stored initial value. When the parent dies, the operating system re-parents the orphaned process to PID 1 (init) or another adoptive process, causing getppid() to return a different value.

/* src/main.c – Parent-process watchdog thread */
static void *parent_watchdog_thread(void *arg) {
    pid_t initial_ppid = *(pid_t *)arg;
    
    while (atomic_load(&g_shutdown) == 0) {
        if (getppid() != initial_ppid) {
            /* Parent died → trigger global shutdown */
            atomic_store(&g_shutdown, 1);
            break;
        }
        usleep(250000);  /* 250 ms poll interval */
    }
    return NULL;
}

When the mismatch is detected, the watchdog sets the global atomic flag g_shutdown to 1, which unblocks the main event loop and triggers cleanup routines.

Worker-Mode Watchdog

Codebase-Memory-MCP spawns dedicated index workers via the --index-worker flag. Each worker creates its own worker watchdog thread using the same parent_watchdog_thread function but monitors the worker's specific parent PID (the MCP server process). This ensures that if the main server crashes, all associated workers terminate immediately rather than continuing to index in isolation.

/* Worker-mode watchdog creation in src/main.c */
cbm_thread_t worker_watchdog_tid;
if (cbm_thread_create(&worker_watchdog_tid,
                      PARENT_WATCHDOG_STACK_SIZE,
                      parent_watchdog_thread,  /* Same function, different arg */
                      &worker_initial_ppid) == 0) {
    cbm_thread_detach(&worker_watchdog_tid);
    cbm_log_info("worker.watchdog.start");
}

Implementation Details in src/main.c

The watchdog is initialized early in the main() function with a minimal stack footprint to reduce memory overhead:

/* src/main.c – Watchdog initialization */
#define PARENT_WATCHDOG_STACK_SIZE (64 * 1024)  /* 64 KB stack */

pid_t parent_initial_ppid = getppid();
cbm_thread_t parent_watchdog_tid;
bool parent_watchdog_started = false;

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

/* During shutdown */
if (parent_watchdog_started) {
    cbm_thread_join(&parent_watchdog_tid);
}

The 64 KB stack size (PARENT_WATCHDOG_STACK_SIZE) is sufficient for the simple polling loop without consuming excessive memory. The thread is detached after creation to allow independent execution, then joined during the shutdown sequence to ensure clean exit.

Testing the Watchdog Behavior

The repository includes regression tests that verify the watchdog exits within seconds of parent termination. These tests are POSIX-specific and skip on Windows environments.

The tests/test_parent_watchdog.sh script launches the MCP server in a subshell, kills the parent process, and asserts that the child exits cleanly:

#!/usr/bin/env bash

# tests/test_parent_watchdog.sh – Validates parent-death detection

tmpdir=$(mktemp -d)
(
   # Launch MCP server in background

   ./codebase-memory-mcp --server &
   child=$!
   sleep 1
   # Kill parent (this script), forcing re-parenting to init

   kill -9 $$
   wait $child && echo "WATCHDOG_EXITED" || echo "FAILED"
) >"$tmpdir/out"

grep -q "WATCHDOG_EXITED" "$tmpdir/out" && exit 0 || exit 1

A corresponding tests/test_worker_watchdog.sh validates that index workers terminate when their MCP server parent dies, ensuring no stray worker processes remain after a server crash.

Summary

  • Parent-process watchdog prevents orphaned Codebase-Memory-MCP processes by monitoring getppid() in a dedicated thread.
  • Polling interval of 250 milliseconds balances responsiveness with minimal CPU usage.
  • Atomic shutdown flag (g_shutdown) ensures thread-safe communication between the watchdog and main event loop.
  • Worker-mode support extends the same protection to --index-worker subprocesses.
  • POSIX-only implementation relies on process semantics not available on Windows platforms.
  • 64 KB stack keeps the watchdog lightweight while ensuring reliable termination detection.

Frequently Asked Questions

What happens if the parent process dies while Codebase-Memory-MCP is indexing?

The watchdog thread detects the parent PID change within 250 milliseconds, sets the global g_shutdown flag, and triggers a graceful shutdown sequence. This interrupts active indexing operations, releases SQLite locks, and allows the process to exit cleanly rather than becoming orphaned.

How does the worker watchdog differ from the main parent watchdog?

Both use the same parent_watchdog_thread function in src/main.c, but the worker watchdog receives the worker process's initial parent PID as its argument. This monitors the MCP server process specifically, ensuring workers terminate if the server dies, whereas the main watchdog monitors the original launcher (terminal, script, or container).

Is the parent-process watchdog available on Windows?

No, the watchdog implementation is POSIX-only because it relies on getppid() and process re-parenting semantics specific to Unix-like systems. The regression tests in tests/test_parent_watchdog.sh and tests/test_worker_watchdog.sh explicitly skip execution on Windows shells.

What is the performance impact of the watchdog polling loop?

The watchdog consumes negligible resources: it allocates only 64 KB of stack space and sleeps for 250 milliseconds between each getppid() check. This results in minimal CPU usage (effectively zero when idle) while ensuring sub-second detection of parent termination.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →