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

The crg-daemon command watches any number of repositories by maintaining a 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 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 (lines 43-45).

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

[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 without requiring manual edits.

Adding a Repository

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), 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

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 and sent a graceful shutdown signal.

Starting the Multi-Repo Daemon

Launch the daemon with the start sub-command:

crg-daemon start

Behind the scenes, _handle_start() in 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
  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

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:

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

Follow logs in real-time:

crg-daemon logs --repo api --follow

Stopping the Daemon

crg-daemon stop

Alternatively, send SIGTERM directly to the PID file:

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 manually or use crg-daemon add/remove, the watcher detects the change and invokes WatchDaemon.reconcile() (lines 52-63 in 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 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:


# 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 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 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →