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-graphmarker) - 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:
- Unless
--foregroundis passed, the process forks into the background - The parent exits; the child initializes
WatchDaemon load_config()(lines 40-49) parseswatch.toml- Each repository is registered with the central
Registry _start_watcher()(lines 38-75) spawns a child process runningcode-review-graph watchfor each repo- 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.tomlwith[daemon]settings and[[repos]]array entries - Use
crg-daemon addandcrg-daemon removefor safe, validated config modifications - The daemon forks automatically unless
--foregroundis specified; PID tracked atdaemon.pid - Each repo gets a dedicated watcher process spawned via
_start_watcher()and monitored via_check_health() - Hot reload via
ConfigWatcherenables dynamic reconfiguration throughreconcile()without restart - Logs are per-repository under the configured
log_dir, accessible viacrg-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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →