# How Daemon Mode in code-review-graph Enables Background Monitoring and Indexing

> Discover how code-review-graph's daemon mode uses a WatchDaemon to continuously monitor repos, rebuild graphs on changes, and restart failures for seamless background operation.

- Repository: [Tirth Kanani/code-review-graph](https://github.com/tirth8205/code-review-graph)
- Tags: how-to-guide
- Published: 2026-08-15

---

**The daemon mode in code-review-graph runs a long-lived `WatchDaemon` process that continuously watches configured repositories, automatically rebuilds graph databases when changes occur, and restarts failed watcher processes to ensure uninterrupted background operation.**

The `code-review-graph` tool provides a complete daemonization system for keeping code review graph databases synchronized with live repositories. According to the `tirth8205/code-review-graph` source code, the implementation centers on the `WatchDaemon` class in [`code_review_graph/daemon.py`](https://github.com/tirth8205/code-review-graph/blob/main/code_review_graph/daemon.py), which handles everything from Unix double-forking to health-checking child processes.

## Daemonization: Detaching from the Terminal

The foundation of background operation is `WatchDaemon.daemonize()` (lines 923-975). This method implements a **classic double-fork pattern** on Unix systems: the parent forks once, the first child forks again, and the original parent and first child exit, leaving a properly orphaned grandchild that the init system adopts.

```python

# From code_review_graph/daemon.py

daemon = WatchDaemon()
daemon.daemonize()  # Double-fork, write PID file, redirect streams

```

The method performs several critical setup steps:

- **PID file creation** via `default_pid_path()` — stores the daemon's process ID for later management
- **Stream redirection** — stdout and stderr are redirected to a log file to prevent terminal attachment issues
- **Signal handlers** — SIGTERM and SIGHUP handlers are installed for graceful shutdown and config reload

On Windows, `daemonize()` falls back to foreground execution with a warning, since Windows lacks equivalent fork semantics.

## Configuration Loading and Repository Registration

Before starting watchers, the daemon loads its configuration through `load_config` (lines 40-66). The configuration is stored as a **TOML file** listing repositories to monitor:

```toml

# ~/.code-review-graph/watch.toml example

[[repositories]]
alias = "main-project"
path = "/home/dev/projects/main"

[[repositories]]
alias = "legacy-api"
path = "/home/dev/projects/legacy"

```

The `WatchDaemon` maintains an internal registry of these repositories and can detect **runtime configuration changes** through the `ConfigWatcher` class (lines 333-382). This component either uses the `watchdog` library for efficient inotify-based watching or falls back to a polling thread when `watchdog` is unavailable.

When [`watch.toml`](https://github.com/tirth8205/code-review-graph/blob/main/watch.toml) is modified, the daemon calls `_on_config_change()` which triggers `reconcile()` — dynamically adding, removing, or updating child watchers without requiring a full restart.

## Initial Graph Building

When `WatchDaemon.start()` is invoked, it first checks each configured repository for an existing graph database at `.code-review-graph/graph.db`. For any repo lacking this file, the daemon runs `_initial_build()` (lines 997-1026):

```python

# Internal _initial_build spawns a subprocess

def _initial_build(self, repo_path: Path, alias: str):
    # Executes: code-review-graph build --repo <path>

    # Creates the initial graph.db before continuous watching begins

```

This subprocess approach isolates the potentially lengthy initial indexing from the daemon's main control loop, preventing blocking operations from delaying health checks or config reloads.

## Background Child Process Management

The core monitoring capability comes from persistent **watcher processes** spawned via `_start_watcher()` (lines 1038-1075). For each repository, the daemon:

1. Starts a child process running `code-review-graph watch --repo <path>`
2. Redirects the child's output to `<log_dir>/<alias>.log`
3. Stores the `subprocess.Popen` object in the `_children` dictionary mapped by alias

```python

# Daemon maintains running watchers

_children: Dict[str, subprocess.Popen] = {
    "main-project": <Popen object>,
    "legacy-api": <Popen object>
}

```

These child processes perform the actual **file-system watching** and **incremental graph updates** for individual repositories, while the parent daemon focuses on orchestration.

## Health Check Loop and Automatic Recovery

The daemon includes a **periodic health-check thread** (`_health_loop`, lines 997-1004) that monitors child process status:

```python
def _health_loop(self):
    while self._running:
        self._check_health()  # Polls each child's .poll() status

        time.sleep(self.health_check_interval)

```

If `_check_health()` discovers a child process has terminated (non-None poll result), the daemon automatically restarts it via `_start_watcher()` and updates its state file. This ensures **self-healing operation** — temporary failures in individual repository watchers don't require manual intervention.

## Cross-Process Status Visibility

For management and debugging, the daemon maintains a **JSON state file** through `_save_state` (lines 1213-1225):

```json
{
  "pid": 18473,
  "children": {
    "main-project": {"pid": 18474, "path": "/home/dev/projects/main"},
    "legacy-api": {"pid": 18475, "path": "/home/dev/projects/legacy"}
  },
  "started_at": "2024-01-15T09:23:17Z"
}

```

CLI commands like `code-review-graph daemon status` read this file and use the cross-platform `pid_alive()` helper to report actual process status, even when invoked from completely separate shell sessions. This provides **visibility into background operations** without requiring direct communication with the daemon process.

## CLI Usage Patterns

The daemon can be controlled through the CLI interface in [`code_review_graph/daemon_cli.py`](https://github.com/tirth8205/code-review-graph/blob/main/code_review_graph/daemon_cli.py):

```bash

# Start the daemon in background

code-review-graph daemon start --daemon

# Check daemon and watcher status

code-review-graph daemon status

# Stop the daemon (reads PID file and sends SIGTERM)

code-review-graph daemon stop

```

The `--daemon` flag to `start` internally invokes `WatchDaemon.daemonize()` before calling `start()` and `run_forever()`.

## Programmatic Control

For integration with other tools, the daemon can be controlled directly from Python:

```python
from code_review_graph.daemon import WatchDaemon, add_repo_to_config

# Start daemon programmatically

daemon = WatchDaemon()
daemon.daemonize()
daemon.start()
daemon.run_forever()  # Blocks until SIGTERM received

```

Adding repositories dynamically without restarting:

```python
from code_review_graph.daemon import add_repo_to_config

# Updates watch.toml; ConfigWatcher detects change automatically

add_repo_to_config("/new/repo/path", alias="new-project")

```

This triggers the daemon's `reconcile()` method to spawn a new watcher process for the added repository.

## Summary

The daemon mode in `code-review-graph` provides comprehensive background operation through:

- **Process detachment** via double-fork Unix daemonization with PID file management
- **Automatic initialization** that builds graph databases for new repositories before continuous monitoring
- **Isolated watcher processes** per repository, with dedicated log files and failure domains
- **Live configuration reloading** that adapts to repository additions and removals without restart
- **Health-check monitoring** with automatic process restart for self-healing operation
- **Persistent state files** enabling cross-process status queries and management

These capabilities eliminate the need for external tools like `tmux` or `screen` while ensuring code review graphs remain synchronized with repository changes.

## Frequently Asked Questions

### How does the daemon handle Windows systems without fork support?

On Windows, `WatchDaemon.daemonize()` detects the platform and falls back to foreground execution with a user warning. The rest of the daemon functionality — child process management, health checks, and config watching — continues to work normally, but the process remains attached to the console. Production Windows deployments typically use Windows Service wrappers or scheduled tasks to achieve background operation.

### What happens when a repository watcher process crashes?

The `_health_loop` thread polls each child's `Popen.poll()` status every few seconds. When a non-None return code is detected, `_check_health()` automatically invokes `_start_watcher()` to spawn a replacement process for that specific repository. The failure is logged, and the state file is updated with the new child PID. This per-repository isolation means one misbehaving repo cannot destabilize monitoring for others.

### Can I change which repositories are watched without restarting the daemon?

Yes. The `ConfigWatcher` component monitors [`watch.toml`](https://github.com/tirth8205/code-review-graph/blob/main/watch.toml) for modifications using either `watchdog` inotify events or polling. When the file changes, the daemon reloads its configuration and runs `reconcile()` to compute differences — starting watchers for newly added repos, terminating removed ones, and restarting repos whose paths changed. You can also use `add_repo_to_config()` from Python code to trigger this flow programmatically.

### How do I check if the daemon is actually running?

The CLI command `code-review-graph daemon status` reads the daemon's JSON state file and verifies each declared PID using `pid_alive()`, a cross-platform helper that checks process existence without requiring signals. This works even from separate terminal sessions or CI pipelines. The output shows the daemon PID, its start time, and the status of each individual repository watcher.