# How the Multi-Repo Daemon in code-review-graph Watches and Updates Multiple Projects

> Discover how the multi-repo daemon in code-review-graph efficiently watches and updates multiple projects using a parent-child process architecture and filesystem monitoring.

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

---

**The `WatchDaemon` class implements a parent-child process architecture that loads repository configurations from [`watch.toml`](https://github.com/tirth8205/code-review-graph/blob/main/watch.toml), spawns isolated subprocess watchers for each project via `_start_watcher()`, and dynamically reconciles changes through filesystem monitoring and periodic health checks.**

The `code-review-graph` tool provides a robust multi-repo daemon capable of monitoring an arbitrary number of Git repositories simultaneously. Unlike single-process solutions, this design isolates each project watcher in its own subprocess to prevent cascading failures, while dynamically managing their lifecycles as configurations change.

## Loading Configuration from watch.toml

The daemon initializes by reading a TOML configuration file through the `load_config()` function. This parses [`watch.toml`](https://github.com/tirth8205/code-review-graph/blob/main/watch.toml) (typically located under `~/.code-review-graph` via `crg_home()` in [`code_review_graph/constants.py`](https://github.com/tirth8205/code-review-graph/blob/main/code_review_graph/constants.py)) into a `DaemonConfig` object containing a list of `WatchRepo` instances.

Each `WatchRepo` encapsulates the repository path, optional alias, and database settings required for graph construction. The daemon validates these paths during startup, ensuring that only accessible repositories are processed before any subprocesses are spawned.

## Spawning Isolated Child Watchers

For every configured repository, the daemon invokes `_start_watcher()` to create an isolated monitoring environment. This method constructs a command vector (`code-review-graph watch --repo <path>`), redirects the child’s stdout and stderr to dedicated per-repo log files, and stores the resulting `Popen` handle in the internal `_children` dictionary.

This subprocess isolation is critical for multi-repository scaling. Because each watcher operates independently, a segmentation fault or infinite loop in one repository’s analyzer cannot compromise the daemon’s ability to monitor others. The implementation in [`code_review_graph/daemon.py`](https://github.com/tirth8205/code-review-graph/blob/main/code_review_graph/daemon.py) handles binary detection automatically, falling back to module entry-points when the compiled binary is unavailable.

## Dynamic Reconciliation with ConfigWatcher

The daemon supports hot-reloading of configurations through the `ConfigWatcher` class. Using the *watchdog* library when available (or a polling fallback), this component monitors [`watch.toml`](https://github.com/tirth8205/code-review-graph/blob/main/watch.toml) for modifications. Upon detecting a change, it triggers `reconcile()`, which performs a three-way diff between the current and new configuration states:

1. **Detects deltas** – Identifies added, removed, or renamed repositories by comparing `WatchRepo` identifiers
2. **Updates registry** – Registers new repositories in the central `Registry` ([`code_review_graph/registry.py`](https://github.com/tirth8205/code-review-graph/blob/main/code_review_graph/registry.py))
3. **Triggers builds** – Executes `_initial_build()` for repositories lacking an existing graph database
4. **Manages processes** – Starts new watchers, gracefully terminates removed ones, and restarts modified configurations

This reconciliation loop ensures that editing [`watch.toml`](https://github.com/tirth8205/code-review-graph/blob/main/watch.toml) never requires a full daemon restart, enabling zero-downtime configuration updates in production deployments.

## Health Monitoring and Automatic Recovery

A background `_health_loop` thread executes every 30 seconds to verify the integrity of the child process pool. The `_check_health()` method iterates over the `_children` dictionary, checking process exit codes. If a watcher has terminated unexpectedly, the daemon automatically invokes `_start_watcher()` to respawn it and calls `_save_state()` to persist the new PID to [`daemon-state.json`](https://github.com/tirth8205/code-review-graph/blob/main/daemon-state.json).

This self-healing mechanism, combined with state persistence, allows the daemon to recover from crashes and provides the `is_daemon_running()` utility function with accurate runtime information for external CLI queries.

## Practical Usage Examples

Initialize and start the daemon in the foreground:

```python
from code_review_graph.daemon import WatchDaemon

daemon = WatchDaemon()       # Loads watch.toml from default location

daemon.start()               # Spawns watchers for all configured repos

daemon.run_forever()         # Blocks main thread; monitors children

```

Run as a background service on Unix systems:

```python
daemon.daemonize()           # Forks process, writes PID file

daemon.run_forever()

```

Add a repository programmatically and trigger hot-reload:

```python
from code_review_graph.daemon import add_repo_to_config

add_repo_to_config("~/projects/myapp", alias="myapp")

# ConfigWatcher detects the change automatically; alternatively:

daemon.reconcile()

```

Query daemon status from external processes:

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

if is_daemon_running():
    print(WatchDaemon().status())

```

## Summary

- The **multi-repo daemon** uses the `WatchDaemon` class in [`code_review_graph/daemon.py`](https://github.com/tirth8205/code-review-graph/blob/main/code_review_graph/daemon.py) to orchestrate multiple repository watchers through subprocess isolation.
- Configuration is defined in **[`watch.toml`](https://github.com/tirth8205/code-review-graph/blob/main/watch.toml)** and parsed into `DaemonConfig` objects, with each repository represented as a `WatchRepo`.
- The **`_start_watcher()`** method launches isolated child processes that execute `code-review-graph watch` commands and log to per-repository files.
- **Dynamic reconciliation** via `reconcile()` and `ConfigWatcher` enables runtime configuration updates without daemon restarts.
- **Health checks** every 30 seconds via `_health_loop` automatically restart failed watchers and persist state to [`daemon-state.json`](https://github.com/tirth8205/code-review-graph/blob/main/daemon-state.json).

## Frequently Asked Questions

### How does the multi-repo daemon handle configuration changes without restarting?

The `ConfigWatcher` monitors [`watch.toml`](https://github.com/tirth8205/code-review-graph/blob/main/watch.toml) using filesystem events (via *watchdog*) or polling intervals. When changes are detected, it invokes `reconcile()` in [`code_review_graph/daemon.py`](https://github.com/tirth8205/code-review-graph/blob/main/code_review_graph/daemon.py), which calculates differences between the current and new configurations, then starts, stops, or restarts child watcher processes accordingly.

### What happens if an individual repository watcher crashes?

Every 30 seconds, the `_health_loop` thread calls `_check_health()` to verify all entries in `_children` are responsive. If a process has exited, the daemon automatically respawns it via `_start_watcher()` and updates [`daemon-state.json`](https://github.com/tirth8205/code-review-graph/blob/main/daemon-state.json), ensuring continuous monitoring without manual intervention.

### Can the daemon scale to monitor dozens of repositories simultaneously?

Yes. The architecture intentionally isolates each repository in its own subprocess, preventing resource contention and failure propagation between projects. This design scales linearly with repository count and eliminates the need for external terminal multiplexers like *tmux* or *screen*.

### Where does the daemon store its runtime state and logs?

The daemon persists child process PIDs and repository paths to **[`daemon-state.json`](https://github.com/tirth8205/code-review-graph/blob/main/daemon-state.json)** (located via `crg_home()` in [`code_review_graph/constants.py`](https://github.com/tirth8205/code-review-graph/blob/main/code_review_graph/constants.py)). Per-repository logs are written to separate files during `_start_watcher()` initialization, enabling isolated debugging for each monitored project.