How the CRG Multi-Repo Daemon Functions: Architecture and Process Supervision
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. 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 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, 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:
is_daemon_running()checks the PID file to prevent duplicate instances.load_config()readswatch.tomlto determine which repositories to watch.WatchDaemon(config)instantiates the supervisor.- If
--foregroundis not set, the process forks viadaemon.daemonize(); otherwise, it remains attached to the terminal. daemon.start()spawns a watcher subprocess for each repository entry and stores the child PIDs in an in-memory state.- A health-check thread launches to monitor the child processes.
daemon.run_forever()blocks the main thread, periodically invoking the health-check loop.
Per-Repository Watcher Processes
Each repository defined in 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 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 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:
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 and spawns a new watcher automatically. Removal works similarly:
crg-daemon remove service
# or by path
crg-daemon remove ~/projects/my-service
The remove_repo_from_config() function rewrites 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:
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:
crg-daemon logs
To tail a specific repository’s log in real time:
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:
crg-daemon start
Run in foreground mode for debugging, keeping the process attached to your terminal:
crg-daemon start --foreground
Add a repository with a memorable alias:
crg-daemon add ~/projects/api-gateway --alias gateway
Check the health of all watchers:
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:
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.pydispatches commands, while theWatchDaemonclass incode_review_graph/daemon.pyhandles process management. - Configuration is stored in
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
SIGTERMor forceful termination viaSIGKILL.
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, 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 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.
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 →