feat: add crg-daemon multi-repo watch daemon#167
Open
imkarrer wants to merge 8 commits intotirth8205:mainfrom
Open
feat: add crg-daemon multi-repo watch daemon#167imkarrer wants to merge 8 commits intotirth8205:mainfrom
imkarrer wants to merge 8 commits intotirth8205:mainfrom
Conversation
…ild process management Add a daemon that reads a TOML config listing repos to watch, spawns child processes (subprocess.Popen) running 'code-review-graph watch' per repo, monitors config for changes with auto-reconciliation, health-checks children with auto-restart, and exposes CLI commands for start/stop/restart/status/logs/add/remove. New files: - code_review_graph/daemon.py: DaemonConfig, WatchDaemon, ConfigWatcher, PID management, daemonization (double-fork), signal handling - code_review_graph/daemon_cli.py: CLI entry point for crg-daemon Modified: - cli.py: add 'daemon' subcommand to main CLI - pyproject.toml: add crg-daemon entry point and tomli dependency
Cover config parsing, PID management, WatchDaemon lifecycle, health checking with auto-restart, reconciliation, and CLI handlers. All tests use unittest.mock to mock subprocess.Popen.
…P.md Document daemon CLI commands, configuration format, and usage examples. Update ROADMAP.md v2.2.0 feature description.
Clarify the daemon's value prop for editors without hooks (Cursor, OpenCode), add numbered quick-setup steps, inline a TOML config example, and link to COMMANDS.md for full reference.
The status CLI was instantiating a fresh WatchDaemon with an empty _children dict, so all repos appeared dead even when watcher processes were confirmed running via ps. Fix: the daemon now persists child PIDs to daemon-state.json whenever children are started, stopped, or restarted. The status command (and WatchDaemon.status()) reads this state file and checks liveness via os.kill(pid, 0) when running cross-process. Added 4 regression tests covering state persistence on start, health check state updates, cross-process status reads, and the CLI handler.
added 2 commits
April 9, 2026 15:03
Previously, adding a new repo to watch.toml while the daemon was running would spawn a watcher child without building the graph or registering the repo in the central registry. This forced users to manually run 'code-review-graph build' for newly added repos. reconcile() now mirrors start() by calling Registry.register() and _initial_build() for repos in to_add and to_update. The build runs outside the lock to avoid blocking health checks.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a multi-repo watch daemon (
crg-daemon) that spawns child processes (subprocess.Popen) each runningcode-review-graph watchfor a configured repository. The daemon reads its configuration from a TOML file, monitors it for live changes, health-checks running watchers, and auto-restarts dead ones. No external dependencies (no tmux, no screen) — pure Python stdlib process management.Motivation
The graph can go stale when changes occur outside agent-driven updates — manual edits, branch switches, rebases, or pulls from teammates. The daemon orchestrates watchers automatically across configured repos so the graph stays current without relying on hooks or manual rebuilds.
Related: #36, #59.
Changes
New files
code_review_graph/daemon.pyDaemonConfig/WatchRepodataclasses, TOML load/save,ConfigWatcher,WatchDaemon(start/stop/reconcile/daemonize/health-check/run_forever), PID management,subprocess.Popenchild process management withthreading.Lock, persisted state file (daemon-state.json) for cross-process status queriescode_review_graph/daemon_cli.pycrg-daemonargparse CLI with 7 subcommands:start,stop,restart,status,logs,add,remove;statusreads persisted state to report live/dead watcherstests/test_daemon.pyModified files
code_review_graph/cli.pydaemonsubparser group with 7 subcommands delegating todaemon_clihandlers; updated module docstringpyproject.tomlcrg-daemonentry point in[project.scripts]; addedtomliconditional dependency for Python < 3.11README.mddocs/COMMANDS.mdcrg-daemonreference with config exampledocs/ROADMAP.mdHow it works
~/.code-review-graph/watch.tomlcrg-daemon startspawns onesubprocess.Popenchild per repo, each runningcode-review-graph watchwith stdout/stderr redirected to per-repo log fileswatch.tomlfor changes and reconciles child processes (starts new repos, terminates removed ones)graph.dbdoes not yet exist — no manualcode-review-graph buildneededproc.poll()) are automatically restartedcrg-daemon start(without--foreground) double-forks to daemonize, writing a PID file for lifecycle management_terminate_child()sends SIGTERM, waits up to 5 seconds, then escalates to SIGKILL_children: dict[str, Popen]viathreading.Lockdaemon-state.jsonon every mutation;crg-daemon statusreads this file and probes PIDs withos.kill(pid, 0)to report alive/dead without needing the daemon object in-memoryUsage
Also available as
code-review-graph daemon start|stop|status|....Testing
tests/test_daemon.pyuv run pytest tests/test_daemon.py -vcli.py)Checklist
mainuv run pytest)uv run ruff check code_review_graph/)from __future__ import annotations, type hints,logging.getLogger(__name__), noshell=True, parameterized SQLreconcile()auto-builds graphs and registers new repos (parity withstart())