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

> Discover how the multi-repo daemon updates code-review-graph graphs. It monitors repos, handles config changes, and ensures graph consistency through automated processes.

- Repository: [Tirth Kanani/code-review-graph](https://github.com/tirth8205/code-review-graph)
- Tags: internals
- Published: 2026-07-25

---

**TLDR:** The multi-repo daemon continuously monitors configured repositories by spawning isolated `code-review-graph watch` subprocesses for each repo, automatically handles configuration changes through reconciliation cycles, and ensures graph consistency via health checks and initial build processes.

The `code-review-graph` toolset provides a robust daemon architecture designed to maintain up-to-date code review graphs across multiple repositories without manual intervention. The **multi-repo watch daemon** orchestrates this by combining process management, file system monitoring, and state persistence to create a resilient background service. This implementation delegates the heavy lifting of file watching to child processes while the parent daemon coordinates configuration changes and system health.

## Configuration Loading and State Persistence

The daemon initializes by reading `~/.code-review-graph/watch.toml`, a TOML configuration file that defines the repository list, aliases, and daemon-wide settings. The `load_config` function in [`code_review_graph/daemon.py`](https://github.com/tirth8205/code-review-graph/blob/main/code_review_graph/daemon.py) parses this file and constructs a `DaemonConfig` object that drives subsequent behavior.

State persistence ensures the daemon remains tractable across process boundaries. The system writes its process ID to `~/.code-review-graph/daemon.pid` and maintains child process information in [`daemon-state.json`](https://github.com/tirth8205/code-review-graph/blob/main/daemon-state.json). Utility functions including `write_pid`, `read_pid`, `_save_state`, and `load_state` manage these files, enabling external CLI commands to query status even when running in separate processes.

## Spawning Repository Watchers

For each repository defined in the configuration, the daemon delegates file system monitoring to dedicated child processes. The `_start_watcher` method constructs and launches a `code-review-graph watch` subprocess for each repo, recording process handles in the internal `_children` mapping.

Before initiating the watch process, the daemon verifies whether the repository requires an initial graph build. The `_initial_build` method executes `code-review-graph build` for repos lacking existing databases, ensuring the graph exists before incremental updates begin. This separation of concerns allows the lightweight watcher processes to focus exclusively on detecting file changes while the parent manages lifecycle complexity.

## Health Monitoring and Automatic Recovery

The daemon implements a supervisory pattern through the `_health_loop` thread, which executes every 30 seconds as defined by `_HEALTH_CHECK_INTERVAL`. The `_check_health` method verifies each child process status, automatically respawning any watcher that has terminated unexpectedly and refreshing the state file with new process IDs.

This self-healing mechanism ensures that temporary failures in individual repository watchers do not compromise the overall monitoring system. The parent process maintains authority over process lifetime while remaining resilient to subprocess crashes.

## Dynamic Configuration Updates

Configuration changes trigger a reconciliation workflow that aligns running processes with the updated [`watch.toml`](https://github.com/tirth8205/code-review-graph/blob/main/watch.toml). The `ConfigWatcher` class monitors the configuration file using the `watchdog` library when available, falling back to a polling thread via `_start_polling` on systems where `watchdog` is unavailable.

When `ConfigWatcher._on_config_changed` detects modifications, it invokes `WatchDaemon._on_config_change`, which reloads the configuration and executes `WatchDaemon.reconcile`. The reconciliation algorithm computes set differences between desired and current repository sets, performing three critical operations:

- **Adding** new watchers for newly configured repositories, including initial builds when necessary
- **Removing** stale watchers and terminating their associated processes
- **Updating** existing watchers when repository paths change

After reconciliation, the daemon persists the new state to ensure consistency across restarts.

## Daemonization and Cross-Platform Support

On POSIX platforms, the daemon supports background operation through the `daemonize` method, which performs a double-fork to detach from the controlling terminal. The `_setup_signal_handlers` method installs handlers for graceful shutdown, ensuring child processes terminate cleanly and PID files are removed.

Cross-platform compatibility requires platform-specific process handling. On **Windows**, the daemon operates strictly in foreground mode without forking, utilizing `_pid_alive_windows` within the `pid_alive` function to circumvent `os.kill` limitations on that platform. This ensures reliable process status detection across operating systems.

## Practical Usage Examples

The daemon exposes both programmatic and command-line interfaces for operational control.

Start the daemon programmatically in the foreground:

```python
from code_review_graph.daemon import WatchDaemon

daemon = WatchDaemon()
daemon.start()                 # spawns one watcher per repo

daemon.run_forever()           # blocks until Ctrl-C or SIGTERM

```

Run as a background service via CLI:

```bash
code-review-graph daemon start --daemonize

# Forks, writes ~/.code-review-graph/daemon.pid,

# and returns control to the shell

```

Add repositories dynamically:

```python
from code_review_graph.daemon import add_repo_to_config

add_repo_to_config("~/projects/my-lib", alias="mylib")

# ConfigWatcher detects the change automatically

# and starts a new watcher process

```

Query status from external processes:

```bash
code-review-graph daemon status --json

# Returns: {"session_name":"crg-watch","running":true,"repos":[...]}

```

Force manual reconciliation after bulk configuration edits:

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

cfg = load_config()               # reads current watch.toml

daemon = WatchDaemon(cfg)
daemon.reconcile()                # aligns watchers with configuration

```

## Summary

- The multi-repo daemon centralizes repository monitoring through a parent process that manages multiple `code-review-graph watch` child processes
- Configuration resides in `~/.code-review-graph/watch.toml`, parsed by `load_config` and monitored via `ConfigWatcher` with automatic reconciliation
- Process state persists through `daemon.pid` and [`daemon-state.json`](https://github.com/tirth8205/code-review-graph/blob/main/daemon-state.json), enabling external status queries and crash recovery
- Health checks run every 30 seconds via `_health_loop`, with automatic restart of failed watchers through `_check_health`
- The architecture supports both foreground operation and POSIX daemonization, with Windows-specific process handling for cross-platform compatibility

## Frequently Asked Questions

### How does the daemon handle configuration file changes while running?

The `ConfigWatcher` class monitors `~/.code-review-graph/watch.toml` using the `watchdog` library for event-driven updates, falling back to polling via `_start_polling` if `watchdog` is absent. When changes occur, `WatchDaemon.reconcile` computes differences between the current and desired repository sets, then adds, removes, or updates watchers accordingly without requiring a full daemon restart.

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

The daemon's `_health_loop` thread checks child process status every 30 seconds using `_check_health`. If a watcher has terminated, the daemon automatically invokes `_start_watcher` to respawn the process for that specific repository, preserving the monitoring coverage for other repos while logging the recovery event.

### Can the daemon run on Windows, and what limitations exist?

Yes, the daemon supports Windows but with specific adaptations. It runs exclusively in foreground mode on Windows (no double-fork daemonization), and uses `_pid_alive_windows` within the `pid_alive` utility to check process status without sending signals, accommodating Windows' different process management semantics compared to POSIX systems.

### Where does the daemon store its runtime state information?

Runtime state persists in two locations: `~/.code-review-graph/daemon.pid` contains the parent daemon's process ID for lifecycle management, while [`daemon-state.json`](https://github.com/tirth8205/code-review-graph/blob/main/daemon-state.json) tracks active child watcher processes. These files enable the `status` command to report daemon health from separate CLI instances and facilitate graceful shutdown sequences.