feat(config): add watcher_enabled config key to disable the background watcher (#335) - #1105
feat(config): add watcher_enabled config key to disable the background watcher (#335)#1105nguiaSoren wants to merge 2 commits into
Conversation
|
Thanks for implementing the approved #335 contract and keeping the change focused. The exact head is security-clean, DCO-compliant, and CI is green. Before final review, two verification gaps need tightening:
The separate key itself was explicitly approved in #335, so no redesign to overload |
…-read docs (DeusData#335) Follow-up to the PR DeusData#1105 review: the unit test proves the config predicate but would still pass even if main()'s gate were removed. Add a process-level regression that drives the real MCP server binary and proves the gate actually prevents watcher initialization when disabled. - tests/test_watcher_disabled.sh (wired into scripts/test.sh as Step 5c) drives the server with an isolated CBM_CACHE_DIR + tiny git fixture (CBM_INDEX_SUPERVISOR=0 for in-process determinism). With watcher_enabled=false it asserts: watcher.disabled reason=config emitted, watcher.start absent, no watcher.watch registration, and manual index_repository served + indexed. A watcher_enabled=true positive control asserts watcher.start + watcher.watch appear, so the test fails if the main() gate is removed (verified by neutering the gate and observing the failure). - docs: CONFIGURATION.md + README.md now document that watcher_enabled (like auto_watch) is read once at process startup, so changing it needs a server restart / MCP reconnect; and clarify auto_watch (session registration) vs watcher_enabled (whether the watcher subsystem starts at all). No production code change: watcher.start and watcher.watch already exist as logs. Signed-off-by: soren <nguiasoren@gmail.com>
|
Thanks for the careful review, @DeusData — both gaps are addressed in the updated head, and I kept it to the switch as you noted. 1. Process-level regression (
The positive control is what makes it bite: I verified it by neutering the 2. Docs. No redesign — the separate key stays as approved in #335. Full |
|
Reviewed in full. The config layer is clean and the contract is exactly right — what needs work is that the gate itself now targets code that no longer exists. You implemented the agreed contract precisely. I checked it against issue #335, where the contract was written out verbatim: default true, false prevents the watcher thread from starting or registering projects, manual Default preservation is exact, which is the thing that would matter most if it were wrong. Coherence when disabled is clean too: the watcher object is simply never created, the thread-create is already gated on The credit I most want to give: you noticed yourself that the unit test would still pass if the What needs re-porting. The daemon rework moved watcher creation out of That makes this a re-port with genuine semantic decisions rather than a conflict resolution, and they are worth thinking about before you write code:
The config layer, the tests, and the docs should all survive largely intact; it is the ~one gate that moves. Two smaller notes:
Nothing here is a design objection — the contract is agreed and your implementation of it is faithful. It is purely that the target moved. |
…tcher (DeusData#335) Add a persistent `watcher_enabled` config key (default true). When false, the daemon host skips building the background watcher entirely: the poll thread never starts and no projects are registered. Manual index_repository is unaffected, and the config wiring stays unconditional so auto_index / auto_watch keep working even when the watcher is off. This is distinct from auto_watch, which only gates per-session registration while the watcher IS running. watcher_enabled is the master switch that stops the subsystem from starting at all — the request in DeusData#335 (doc-heavy repos where every save churns the graph baseline). The key is read once, when the daemon builds its host state. Changing it takes effect when the daemon next starts; docs/CONFIGURATION.md says so explicitly. A disabled watcher is not a daemon-startup failure — the daemon still comes up and still owns IPC, HTTP/UI, project locks, and manual indexing; only the watcher and the in-memory store backing it go unbuilt. - cli.h/cli.c: CBM_CONFIG_WATCHER_ENABLED key + cbm_config_watcher_enabled() predicate (NULL-safe, default true), shown in `config` help + `config list`. - daemon/host.c: gate watcher construction in host_state_prepare() and the thread create in host_background_start(); log watcher.disabled when skipped. Teardown needed no change — stop NULL-checks, join is gated on watcher_started, and cbm_watcher_free is NULL-safe. - test_cli.c: cli_config_watcher_enabled_default_and_persist pins the config layer — default on, disable/re-enable spellings, persistence across reopen, and a NULL-config default (a config-store failure must never silently disable the watcher). - docs: document watcher_enabled, and add the previously-undocumented auto_watch row to CONFIGURATION.md so the watcher knobs are described together; README example. Closes DeusData#335. Signed-off-by: soren <nguiasoren@gmail.com>
…-read docs (DeusData#335) Follow-up to the PR DeusData#1105 review: the unit test proves the config predicate but would still pass even if the daemon-side gate were removed. Add a process-level regression that drives the real binary and proves the gate actually prevents the watcher from being built, started or registered when disabled. - tests/test_watcher_disabled.sh (wired into scripts/test.sh as Step 5c) drives the binary against an isolated CBM_CACHE_DIR + tiny git fixture. With watcher_enabled=false it asserts: watcher.disabled reason=config emitted, watcher.start absent, no watcher.watch registration, auto_index still runs, and manual index_repository is served. A watcher_enabled=true positive control asserts watcher.start + watcher.watch appear, so the test fails if the gate is removed (verified by neutering the gate in host_state_prepare and observing the failure). No assertion is decided by a timeout. The watcher lives in the daemon, which logs to $CBM_CACHE_DIR/logs/cbm-daemon.log in a fixed order: watcher.disabled (host_state_prepare) -> daemon.start -> the thread's own watcher.start -> the join -> daemon.stop. Each run therefore retires its daemon and waits for that CLOSED lifecycle before asserting, so absence is a fact rather than a race. Every wait is a bounded poll on an asserted state following the soak harness's wait_for_daemon_stop doctrine, and an exhausted budget fails loudly with the reason instead of passing quietly; session stdin is held open through a FIFO until the last JSON-RPC response lands, not for an interval. Registration is asynchronous and unreachable until the project DB exists (application_refresh_watch_locked requires application_regular_db_exists), so a closed lifecycle alone would not carry the negative — the session could simply have ended first. The disabled+auto_index run waits for that DB, i.e. registration's own precondition; the enabled control waits for watcher.watch itself, so its absence in the disabled runs is evidence rather than noise. Honors CBM_TEST_BINARY like tests/test_worker_watchdog.sh, so the BUILD_DIR= sanitizer and container legs test the binary they built. - docs: CONFIGURATION.md + README.md document that watcher_enabled is read once when the daemon starts — the daemon outlives individual MCP sessions, so reconnecting a client is not enough; `daemon stop` retires it. This corrects the earlier claim that auto_watch is also startup-read: auto_watch is consulted per registration, and the two keys are now contrasted on read time as well as on scope (session registration vs whether the subsystem starts at all). No production code change in this commit: watcher.start and watcher.watch already exist as production logs. Signed-off-by: soren <nguiasoren@gmail.com>
4efb69c to
ed9a612
Compare
|
Thanks — and you were right on every point. Rebased first as you asked (394 behind by the time I started), then re-ported. Where it went. Key changes while a daemon is running. Read once, when the daemon builds its host state. I considered live re-reading for symmetry with Daemon lifecycle. Unchanged — it still starts and still owns IPC, HTTP/UI, project locks and the application. Only the watcher and the The process test. Rewritten, and your note about the sleep was the more important of the two. No assertion is decided by a timeout now. The daemon logs to One gap that needed care: registration is asynchronous and unreachable until the project DB exists ( It also honours Re-verified the gate-removal check at the new site, since that was the point of the test existing: neutering the host.c gate and rebuilding fails it immediately — The config layer, the unit test and the docs survived the rebase intact; only comments moved from |
Summary
Adds a persistent
watcher_enabledconfig key (defaulttrue) that fullydisables the background watcher when set to
false. Closes #335.Per the maintainer's contract on #335:
watcher_enableddefaults totrue,falseprevents the watcher thread from starting and from registeringprojects, and manual
index_repositorycontinues to work normally. Scope islimited to this one switch — no other watcher policy changes.
Why (and how it differs from
auto_watch)auto_watch(defaulttrue) already gates per-session registration — butthe watcher thread still runs idle even with
auto_watch=false. #335 asksfor a way to stop the watcher entirely (the doc-heavy-repo use case: every save
churns the graph baseline).
watcher_enabledis that master switch: whenfalse,main()never creates the watcher, so the poll thread never starts andnothing is registered.
auto_watchis left unchanged as the finer-grainedper-session registration control. Both are now documented together.
(Note: the watcher polls for git changes now, not raw mtime as the original
0.6.0-era issue described; the user-visible concern — auto-reindex churn — is the
same, and this switch addresses it.)
The change
src/cli/cli.h/src/cli/cli.c—CBM_CONFIG_WATCHER_ENABLED(
"watcher_enabled") plus a small NULL-safe predicatecbm_config_watcher_enabled(cfg)(returns the persisted bool, defaulttrue).Listed in
configusage help andconfig listoutput, alongside the siblingkeys.
src/main.c— gate watcher creation +set_watcheron the predicate; theexisting thread-create is already gated on
g_watcher, so a disabled watchermeans no thread and (via the existing NULL guard in
register_watcher_if_enabled) no registration.cbm_mcp_server_set_configstays outside the gate so
auto_index/auto_watchstill read config whenthe watcher is off. Logs
watcher.disabled reason=configwhen skipped.tests/test_cli.c—cli_config_watcher_enabled_default_and_persistdrives the predicate
main()gates on: default on,false/0/offdisable,true/1/onre-enable, persistence across reopen, and a NULL-config default(a config-store failure must never silently disable the watcher). The existing
dump_verify-style tests feed synthetic values; this exercises the real seam.docs/CONFIGURATION.md/README.md— documentwatcher_enabled, and addthe previously-missing
auto_watchrow so the watcher knobs are describedtogether.
Verification
scripts/test.sh(ASan + UBSan) green: 6329 passed, 1 skipped,including the new test.
CBM_CACHE_DIR):config set watcher_enabled false→ server logswatcher.disabled reason=configand no watcher thread starts; default/true→ watcher startsnormally (
watcher.disabledcount 0); manualindex_repositoryunaffected.Makefile.cbmchange (all touched files already compiled); noconfig setallowlist to update; no CHANGELOG in the repo.
All commits are DCO signed-off.