# How to Set Up a Multi-Repo Daemon to Watch Multiple Projects with code-review-graph

> Easily set up a multi-repo daemon with code-review-graph to monitor multiple projects. Learn how to configure watch.toml and spawn child processes for efficient tracking.

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

---

**The `crg-daemon` command watches any number of repositories by maintaining a [`watch.toml`](https://github.com/tirth8205/code-review-graph/blob/main/watch.toml) configuration file and spawning a dedicated child process for each project.**

The `code-review-graph` tool provides a production-ready daemon designed to monitor multiple codebases simultaneously. Whether you're managing a microservices architecture or maintaining several open-source packages, the multi-repo daemon eliminates the need to run separate watch instances manually. This guide walks through configuring, starting, and managing a centralized watch instance using the official CLI and configuration system.

## Creating the Watch Configuration File

Before starting the daemon, you need a valid [`watch.toml`](https://github.com/tirth8205/code-review-graph/blob/main/watch.toml) file. By default, the daemon looks for this at `~/.code-review-graph/watch.toml`, as defined in `default_config_path()` in [`code_review_graph/daemon.py`](https://github.com/tirth8205/code-review-graph/blob/main/code_review_graph/daemon.py) (lines 43-45).

The minimal configuration requires a `[daemon]` section and at least one `[[repos]]` entry:

```toml
[daemon]
session_name = "crg-watch"
log_dir = "~/.code-review-graph/logs"
poll_interval = 2

[[repos]]
path = "/home/user/projects/service-api"
alias = "api"

[[repos]]
path = "/home/user/projects/web-frontend"

# alias defaults to directory name ("web-frontend")

```

Each repository entry requires:

- **path** – Absolute path to the repository root (must contain `.git`, `.svn`, or `.code-review-graph` marker)
- **alias** – Optional short name for logs and status display; defaults to the directory basename

You can create this file manually or let the daemon generate a stub on first run.

## Adding and Removing Repositories via CLI

The `crg-daemon` CLI provides `add` and `remove` sub-commands that safely modify [`watch.toml`](https://github.com/tirth8205/code-review-graph/blob/main/watch.toml) without requiring manual edits.

### Adding a Repository

```bash
crg-daemon add /absolute/path/to/project-gamma --alias gamma

```

This invokes `add_repo_to_config()` (lines 97-144 in [`code_review_graph/daemon.py`](https://github.com/tirth8205/code-review-graph/blob/main/code_review_graph/daemon.py)), which validates the directory contains a recognized VCS marker before appending to the config. The command outputs confirmation and triggers a hot reload if the daemon is already running.

### Removing a Repository

```bash
crg-daemon remove gamma

```

This calls `remove_repo_from_config()` (lines 45-73), which matches by alias, removes the entry, and terminates the associated watcher process. The child PID is retrieved from [`daemon-state.json`](https://github.com/tirth8205/code-review-graph/blob/main/daemon-state.json) and sent a graceful shutdown signal.

## Starting the Multi-Repo Daemon

Launch the daemon with the `start` sub-command:

```bash
crg-daemon start

```

Behind the scenes, `_handle_start()` in [`code_review_graph/daemon_cli.py`](https://github.com/tirth8205/code-review-graph/blob/main/code_review_graph/daemon_cli.py) (lines 31-53) orchestrates the startup sequence:

1. Unless `--foreground` is passed, the process forks into the background
2. The parent exits; the child initializes `WatchDaemon`
3. `load_config()` (lines 40-49) parses [`watch.toml`](https://github.com/tirth8205/code-review-graph/blob/main/watch.toml)
4. Each repository is registered with the central `Registry`
5. `_start_watcher()` (lines 38-75) spawns a child process running `code-review-graph watch` for each repo
6. The main thread enters `run_forever()`, monitoring for signals and config changes

The daemon writes its PID to `~/.code-review-graph/daemon.pid` via `write_pid()` (lines 80-85) for later management.

## Managing Daemon Lifecycle and Health

### Checking Status

```bash
crg-daemon status

```

This displays the daemon PID, session name, log directory, and a table of all watched repositories with their current state, process IDs, and paths.

### Monitoring Logs

Each repository streams to its own log file (`<alias>.log`) under the configured `log_dir`. The daemon itself logs to `daemon.log`.

View specific repository logs:

```bash
crg-daemon logs --repo api --lines 50

```

Follow logs in real-time:

```bash
crg-daemon logs --repo api --follow

```

### Stopping the Daemon

```bash
crg-daemon stop

```

Alternatively, send SIGTERM directly to the PID file:

```bash
kill -TERM $(cat ~/.code-review-graph/daemon.pid)

```

This triggers graceful shutdown: the daemon terminates all child watchers, saves final state, and removes the PID file.

## Dynamic Reconfiguration Without Restart

The daemon supports hot reloading via `ConfigWatcher`. When you edit [`watch.toml`](https://github.com/tirth8205/code-review-graph/blob/main/watch.toml) manually or use `crg-daemon add/remove`, the watcher detects the change and invokes `WatchDaemon.reconcile()` (lines 52-63 in [`daemon.py`](https://github.com/tirth8205/code-review-graph/blob/main/daemon.py)).

The reconciliation process:

- Compares running child processes against the updated configuration
- Starts watchers for newly added repositories
- Stops and removes watchers for deleted repositories
- Restarts watchers if configuration parameters (like `poll_interval`) change

This enables zero-downtime reconfiguration in production environments.

## Health Monitoring and Auto-Recovery

A background thread continuously verifies child process health through `_check_health()` (lines 5-15). If a watcher process dies unexpectedly, the daemon automatically restarts it and logs the event.

Child PIDs are persisted to [`daemon-state.json`](https://github.com/tirth8205/code-review-graph/blob/main/daemon-state.json) via `_save_state()` (lines 12-20), ensuring the daemon can track and manage processes even across brief restarts or signal handling.

## Directory Structure and Key Files

Understanding the layout helps with troubleshooting:

| Path | Purpose |
|------|---------|
| `~/.code-review-graph/watch.toml` | Master configuration file |
| `~/.code-review-graph/daemon.pid` | Running daemon process identifier |
| `~/.code-review-graph/daemon-state.json` | Serialized child process state |
| `~/.code-review-graph/logs/daemon.log` | Daemon operational logs |
| `~/.code-review-graph/logs/<alias>.log` | Per-repository watcher output |

## Advanced: Running Multiple Daemon Sessions

The `session_name` parameter in `[daemon]` enables isolated daemon instances. By using separate config files with different session names, you can run non-overlapping watch sets:

```bash

# Development projects

crg-daemon --config ~/.code-review-graph/dev.toml start

# Production projects

crg-daemon --config ~/.code-review-graph/prod.toml start

```

Each session maintains independent PID files, state, and log directories based on the session name.

## Summary

- **Configuration lives in `~/.code-review-graph/watch.toml`** with `[daemon]` settings and `[[repos]]` array entries
- **Use `crg-daemon add` and `crg-daemon remove`** for safe, validated config modifications
- **The daemon forks automatically** unless `--foreground` is specified; PID tracked at `daemon.pid`
- **Each repo gets a dedicated watcher process** spawned via `_start_watcher()` and monitored via `_check_health()`
- **Hot reload via `ConfigWatcher`** enables dynamic reconfiguration through `reconcile()` without restart
- **Logs are per-repository** under the configured `log_dir`, accessible via `crg-daemon logs --repo <alias>`

## Frequently Asked Questions

### What happens if I edit [`watch.toml`](https://github.com/tirth8205/code-review-graph/blob/main/watch.toml) while the daemon is running?

The daemon's `ConfigWatcher` detects the file change and triggers `reconcile()` automatically. New repositories spawn watchers immediately; removed repositories have their watchers terminated; unchanged repositories continue running without interruption.

### Can I use relative paths in the `[[repos]]` configuration?

No. The `add_repo_to_config()` function explicitly requires absolute paths, which it validates against VCS markers. Relative paths would break when the daemon forks and changes working directory.

### How do I recover if the daemon crashes and leaves orphaned watcher processes?

On next start, the daemon reads [`daemon-state.json`](https://github.com/tirth8205/code-review-graph/blob/main/daemon-state.json) and attempts to reclaim or terminate previously known child PIDs. If stale PIDs are found, they're logged and ignored. You can manually clean up with `pkill -f "code-review-graph watch"` if needed.

### Does the daemon support repository aliases with spaces or special characters?

Aliases are sanitized during `add_repo_from_config()`. Stick to alphanumeric characters, hyphens, and underscores to ensure log file names remain valid and `crg-daemon status` displays correctly.