How the Multi-Repo Daemon (crg-daemon) Works in code-review-graph
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 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. 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 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:
crg-daemon add ~/projects/my-service --alias service
This appends an entry to 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), 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:
# 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:
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:
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:
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-daemoncommand incode_review_graph/daemon_cli.pyprovides a CLI for managing the background WatchDaemon process. - Configuration persists in
watch.toml, edited viaaddandremovesub-commands that the running daemon detects automatically. - The
WatchDaemonclass incode_review_graph/daemon.pysupervises per-repository watcher subprocesses and automatically restarts failed processes. - Health monitoring occurs through a background thread that checks
pid_alivestatus for each child watcher. - Logging splits between a central
daemon.logand individual<alias>.logfiles 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 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). 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.
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 →