# How the Multi-Repo Daemon (crg-daemon) Works in code-review-graph

> Understand how the multi-repo daemon crg-daemon synchronizes code review graphs across multiple repositories by supervising watcher subprocesses and maintaining persistent configuration.

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

---

**The `crg-daemon` command launches a WatchDaemon process that supervises per-repository watcher subprocesses, automatically restarts failed watchers, and maintains a persistent configuration in [`watch.toml`](https://github.com/tirth8205/code-review-graph/blob/main/watch.toml) to keep code review graphs synchronized across multiple repositories.**

The `crg-daemon` utility in the `tirth8205/code-review-graph` repository provides continuous, background monitoring of multiple Git repositories. Unlike the single-repository mode that relies on Git hooks, this multi-repo daemon runs as a persistent supervisor process, spawning isolated child watchers for each configured repository and ensuring the code review graph remains current without manual intervention.

## Architecture of the WatchDaemon Process

### CLI Entry Point and Command Dispatch

The executable entry point lives in [`code_review_graph/daemon_cli.py`](https://github.com/tirth8205/code-review-graph/blob/main/code_review_graph/daemon_cli.py). This module parses sub-commands—including `start`, `stop`, `restart`, `status`, `logs`, `add`, and `remove`—and delegates each to dedicated handlers. For example, `_handle_start` manages the daemonization logic, while `_handle_status` renders the process table by loading state through `load_state()`.

### Core Supervision Logic

The `WatchDaemon` class in [`code_review_graph/daemon.py`](https://github.com/tirth8205/code-review-graph/blob/main/code_review_graph/daemon.py) implements the long-running supervisor. When initialized via `WatchDaemon(config)`, it loads the daemon configuration and prepares to spawn child processes. The daemon writes its own PID to a PID file (typically located alongside the configuration) to enable later management via `stop` or `restart` commands.

## Configuration and Repository Management

### The watch.toml Configuration File

The daemon persists its state in a TOML file, defaulting to `~/.config/crg/watch.toml`. Each entry specifies a repository *path* and an optional *alias*. The `add` and `remove` sub-commands modify this file dynamically using helper functions defined in the daemon module.

### Adding and Removing Repositories

To register a new repository without restarting the daemon:

```bash
crg-daemon add ~/projects/my-service --alias service

```

This appends an entry to [`watch.toml`](https://github.com/tirth8205/code-review-graph/blob/main/watch.toml). If the daemon is already running, it detects the configuration change automatically and spawns a new watcher subprocess. Conversely, `crg-daemon remove <alias>` rewrites the configuration and terminates the associated watcher.

## Process Supervision and Health Monitoring

### Subprocess Management

On `start`, the `WatchDaemon` creates a dedicated child process for every repository listed in the configuration via `daemon.start()`. Each child executes the standard file-watcher logic (typically from [`code_review_graph/watcher.py`](https://github.com/tirth8205/code-review-graph/blob/main/code_review_graph/watcher.py)), monitoring its assigned repository for changes and updating the graph accordingly. Each subprocess writes to its own log file at `<log_dir>/<alias>.log`.

### Health Checks and Automatic Restart

A background thread within the daemon periodically verifies child PIDs using a `pid_alive` check. If a watcher process dies unexpectedly, the supervisor automatically restarts it, ensuring high availability of the monitoring pipeline. The main thread blocks on `daemon.run_forever()`, which invokes the health-check loop at regular intervals.

### Signal Handling and Daemonization

When invoked without `--foreground`, the daemon forks via `daemon.daemonize()`, detaching from the terminal and running as a background process. In foreground mode (`--foreground`), the process remains attached to the terminal and registers handlers for `SIGINT` and `SIGTERM` to ensure graceful shutdown of all child watchers when the user presses Ctrl-C.

## Logging and Observability

### Central and Per-Repository Logs

The daemon maintains two log streams. The **central daemon log** at `<log_dir>/daemon.log` captures supervisor-level events from the main process. **Per-repository logs** stored as `<log_dir>/<alias>.log` capture file-watcher output for individual repositories, enabling isolated debugging.

### Accessing Logs via CLI

The `logs` sub-command provides convenient access to both streams:

```bash

# View last 50 lines of daemon log

crg-daemon logs

# Follow a specific repository's log in real-time

crg-daemon logs --repo service -f

```

## Daemon Lifecycle and Status Monitoring

### Starting and Stopping the Daemon

To launch the daemon in the background:

```bash
crg-daemon start

```

To stop it gracefully, the CLI reads the PID from the PID file using `read_pid()`, sends `SIGTERM`, and waits up to 5 seconds before forcing termination with `SIGKILL`:

```bash
crg-daemon stop

```

### Checking System Status

The `status` sub-command loads the current configuration and, if the daemon is running, retrieves the persisted state to map aliases to child PIDs. It renders a table showing each repository's alias, alive/dead status, PID, and path:

```bash
crg-daemon status

```

Example output:

```

Daemon:  running (PID 12457)
Name:    my-session
Log dir: /home/user/.cache/crg/logs
Poll:    5s

  Alias   Status   PID      Path
  ------  -------- -------- ---------------------------------
  service alive    12460   /home/user/projects/my-service
  api     dead     -        /home/user/projects/api

```

## Summary

- The `crg-daemon` command in [`code_review_graph/daemon_cli.py`](https://github.com/tirth8205/code-review-graph/blob/main/code_review_graph/daemon_cli.py) provides a CLI for managing the background WatchDaemon process.
- Configuration persists in [`watch.toml`](https://github.com/tirth8205/code-review-graph/blob/main/watch.toml), edited via `add` and `remove` sub-commands that the running daemon detects automatically.
- The `WatchDaemon` class in [`code_review_graph/daemon.py`](https://github.com/tirth8205/code-review-graph/blob/main/code_review_graph/daemon.py) supervises per-repository watcher subprocesses and automatically restarts failed processes.
- Health monitoring occurs through a background thread that checks `pid_alive` status for each child watcher.
- Logging splits between a central `daemon.log` and individual `<alias>.log` files for troubleshooting specific repositories.

## Frequently Asked Questions

### What is the difference between `crg-daemon` and the standard `crg` command?

The standard `crg` command typically runs in Git-hook mode or single-execution mode, processing changes for one repository at a time. In contrast, `crg-daemon` is a persistent multi-repo supervisor that continuously monitors multiple repositories via background subprocesses, eliminating the need for manual invocation or per-repository hook installation.

### How does the daemon detect configuration changes?

The daemon monitors the [`watch.toml`](https://github.com/tirth8205/code-review-graph/blob/main/watch.toml) file for modifications. When you run `crg-daemon add` or `crg-daemon remove`, the CLI updates the configuration file. The running `WatchDaemon` detects these changes and spawns or terminates watcher subprocesses accordingly without requiring a full restart.

### Where does the daemon store its process ID?

The daemon writes its PID to a PID file located in the user's configuration directory (alongside [`watch.toml`](https://github.com/tirth8205/code-review-graph/blob/main/watch.toml)). This file enables the `stop`, `restart`, and `status` commands to locate and signal the running supervisor process. The `is_daemon_running()` function checks this file to prevent multiple instances from starting simultaneously.

### Can I run the daemon without detaching from the terminal?

Yes. Pass the `--foreground` flag to `crg-daemon start` or `crg-daemon restart`. In foreground mode, the process skips the `daemon.daemonize()` fork, remains attached to your terminal, and responds to Ctrl-C for immediate shutdown. This mode is ideal for debugging or development environments.