# How the CRG Multi-Repo Daemon Functions: Architecture and Process Supervision

> Learn how the CRG multi-repo daemon functions by supervising file system watchers across Git repos. It ensures code-review graph synchronization through dedicated child processes and automatic restarts.

- Repository: [Tirth Kanani/code-review-graph](https://github.com/tirth8205/code-review-graph)
- Tags: architecture
- Published: 2026-08-10

---

**The CRG multi-repo daemon (`crg-daemon`) continuously supervises file system watchers across multiple Git repositories, spawning dedicated child processes for each repo and automatically restarting any that fail to keep the code-review graph synchronized.**

The `crg-daemon` command in the `tirth8205/code-review-graph` repository provides a persistent background service for teams managing code review data across many projects. Unlike the single-repository Git-hook mode, this **CRG multi-repo daemon** maintains long-running watchers that detect file changes and update the graph database automatically. The architecture separates the CLI interface from the core supervision logic, using a TOML configuration file to manage the repository portfolio.

## Architecture of the CRG Multi-Repo Daemon

### CLI Entry Point and Command Dispatch

The command-line interface 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—`start`, `stop`, `restart`, `status`, `logs`, `add`, and `remove`—and delegates each to dedicated handlers. For example, `_handle_start` manages the daemonization process, while `_handle_status` loads the current state to report on child process health. The CLI handles PID file management and signal routing before handing control to the core supervisor.

### The WatchDaemon Core

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, it loads the daemon configuration via `load_config()` and prepares to spawn child processes. The daemon writes its own PID to a PID file (default location in the user’s config directory) so that subsequent commands like `stop` or `status` can locate the process. If started with `--foreground`, the daemon registers handlers for `SIGINT` and `SIGTERM` to ensure graceful shutdown of child watchers; otherwise, it calls `daemon.daemonize()` to fork into the background.

### Configuration and State Management

The daemon persists its repository list in [`watch.toml`](https://github.com/tirth8205/code-review-graph/blob/main/watch.toml), typically stored at `~/.config/crg/watch.toml`. Each entry contains a repository *path* and an optional *alias*. The `add` sub-command calls `add_repo_to_config()` to append entries, while `remove` invokes `remove_repo_from_config()` to rewrite the file. The daemon monitors this configuration file for changes, allowing dynamic updates without a full restart.

## Execution Flow and Process Lifecycle

### Starting the Daemon

When you execute `crg-daemon start`, the following occurs:

1. `is_daemon_running()` checks the PID file to prevent duplicate instances.
2. `load_config()` reads [`watch.toml`](https://github.com/tirth8205/code-review-graph/blob/main/watch.toml) to determine which repositories to watch.
3. `WatchDaemon(config)` instantiates the supervisor.
4. If `--foreground` is not set, the process forks via `daemon.daemonize()`; otherwise, it remains attached to the terminal.
5. `daemon.start()` spawns a watcher subprocess for each repository entry and stores the child PIDs in an in-memory state.
6. A health-check thread launches to monitor the child processes.
7. `daemon.run_forever()` blocks the main thread, periodically invoking the health-check loop.

### Per-Repository Watcher Processes

Each repository defined in [`watch.toml`](https://github.com/tirth8205/code-review-graph/blob/main/watch.toml) receives its own dedicated subprocess. These child processes execute the same file-watcher logic found in the standard `crg` CLI (typically implemented in [`code_review_graph/watcher.py`](https://github.com/tirth8205/code-review-graph/blob/main/code_review_graph/watcher.py) or similar), emitting events to the graph engine and writing to dedicated log files at `<log_dir>/<alias>.log`. This isolation ensures that a crash in one repository’s watcher does not affect others.

### Health Checks and Automatic Recovery

The supervisor runs a periodic health-check loop that verifies each child PID is alive using a `pid_alive` check. If a watcher process dies unexpectedly, the **CRG multi-repo daemon** automatically restarts it, ensuring continuous coverage. The daemon also monitors the [`watch.toml`](https://github.com/tirth8205/code-review-graph/blob/main/watch.toml) file itself; when entries are added or removed via the CLI, the supervisor adjusts the child process pool accordingly without requiring a manual restart.

### Stopping and Restarting

The `stop` sub-command reads the daemon PID from the PID file via `read_pid()`, sends `SIGTERM`, and waits up to 5 seconds for graceful termination. If the process persists, it sends `SIGKILL` and clears the PID file. The `restart` command chains these operations, optionally preserving the `--foreground` flag.

## Managing Repositories and Logs

### Adding and Removing Repositories

To expand the watch portfolio without interrupting 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 and spawns a new watcher automatically. Removal works similarly:

```bash
crg-daemon remove service

# or by path

crg-daemon remove ~/projects/my-service

```

The `remove_repo_from_config()` function rewrites [`watch.toml`](https://github.com/tirth8205/code-review-graph/blob/main/watch.toml) without the specified entry, and the supervisor terminates the corresponding child process.

### Monitoring Status and Logs

The `status` sub-command provides real-time visibility:

```bash
crg-daemon status

```

This loads the persisted state via `load_state()` and prints a table showing each alias, whether the child is **alive** or **dead**, the PID, and the repository path. When the daemon is not running, it lists only the configured repositories.

For observability, the daemon maintains a central `daemon.log` and per-repo logs. To view the last 50 lines of the main log:

```bash
crg-daemon logs

```

To tail a specific repository’s log in real time:

```bash
crg-daemon logs --repo service -f

```

The `logs` command determines the appropriate file path based on `config.log_dir` and either tails continuously (`-f`) or outputs the last *N* lines.

## Practical Usage Examples

Start the daemon in the background to supervise all configured repositories:

```bash
crg-daemon start

```

Run in foreground mode for debugging, keeping the process attached to your terminal:

```bash
crg-daemon start --foreground

```

Add a repository with a memorable alias:

```bash
crg-daemon add ~/projects/api-gateway --alias gateway

```

Check the health of all watchers:

```bash
crg-daemon status

```

Typical 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

```

Restart the daemon after configuration changes:

```bash
crg-daemon restart

```

## Summary

- The **CRG multi-repo daemon** (`crg-daemon`) acts as a persistent supervisor for file system watchers across multiple repositories.
- The CLI entry point in [`code_review_graph/daemon_cli.py`](https://github.com/tirth8205/code-review-graph/blob/main/code_review_graph/daemon_cli.py) dispatches commands, while the `WatchDaemon` class in [`code_review_graph/daemon.py`](https://github.com/tirth8205/code-review-graph/blob/main/code_review_graph/daemon.py) handles process management.
- Configuration is stored in [`watch.toml`](https://github.com/tirth8205/code-review-graph/blob/main/watch.toml) (default `~/.config/crg/watch.toml`), which the daemon watches for dynamic updates.
- Each repository runs in an isolated subprocess that logs to `<log_dir>/<alias>.log`, with automatic restart on failure.
- Health checks run continuously to verify child PIDs, and the daemon supports graceful shutdown via `SIGTERM` or forceful termination via `SIGKILL`.

## Frequently Asked Questions

### How does the daemon handle crashes or unexpected watcher termination?

The supervisor implements a health-check thread that periodically verifies each child PID using the `pid_alive` function. If a watcher process exits unexpectedly, the daemon automatically restarts it, logging the event to `daemon.log`. This ensures that transient failures in file system monitoring do not leave repositories unwatched.

### Can I run the CRG multi-repo daemon without backgrounding it?

Yes. Pass the `--foreground` flag to the `start` or `restart` commands. In this mode, the daemon does not fork, remains attached to the terminal, and registers signal handlers for `SIGINT` and `SIGTERM` to allow graceful shutdown via Ctrl-C. This is useful for debugging or when running under a process manager like systemd.

### Where does the daemon store its configuration and logs?

The daemon configuration resides in [`watch.toml`](https://github.com/tirth8205/code-review-graph/blob/main/watch.toml), typically located at `~/.config/crg/watch.toml`. Logs are written to the directory specified by `config.log_dir` (default `~/.cache/crg/logs`), with a central `daemon.log` and individual `<alias>.log` files for each repository watcher.

### How do I add a new repository to an already running daemon?

Use the `add` sub-command: `crg-daemon add <path> --alias <name>`. This updates [`watch.toml`](https://github.com/tirth8205/code-review-graph/blob/main/watch.toml) on disk. The running daemon detects the file change and spawns a new watcher process for the added repository without requiring a restart, ensuring zero-downtime configuration updates.