MCPcopy Index your code
hub / github.com/colbymchenry/codegraph / start

Method start

src/sync/watcher.ts:327–391  ·  view source on GitHub ↗

* Start watching for file changes. * Returns true if watching started successfully, false otherwise.

()

Source from the content-addressed store, hash-verified

325 * Returns true if watching started successfully, false otherwise.
326 */
327 start(): boolean {
328 if (this.recursiveWatcher || this.dirWatchers.size > 0 || this.inert) return true; // Already watching
329 this.stopped = false;
330 this.degradedReason = null;
331 this.lockRetryCount = 0;
332
333 // Some environments make filesystem watching unusable — most notably
334 // WSL2 /mnt/ drives, where the underlying fs.watch calls block long
335 // enough to break MCP startup handshakes (issue #199). Skip watching
336 // there; callers fall back to manual `codegraph sync` or git sync hooks.
337 const disabledReason = watchDisabledReason(this.projectRoot);
338 if (disabledReason) {
339 logDebug('File watcher disabled', { reason: disabledReason, projectRoot: this.projectRoot });
340 return false;
341 }
342
343 // Reuse the indexer's ignore set so the watcher and indexer agree on scope.
344 this.ignoreMatcher = buildScopeIgnore(this.projectRoot);
345
346 try {
347 if (this.inertForTests) {
348 // Test-only: install no OS watcher; the seam drives events instead.
349 this.inert = true;
350 } else if (supportsRecursiveWatch()) {
351 this.startRecursive();
352 } else {
353 this.startPerDirectory();
354 }
355
356 // The per-directory (Linux) path catches watch-resource exhaustion inside
357 // watchTree and degrades synchronously rather than throwing, so it never
358 // reaches the catch below. Surface that as a failed start here so both
359 // strategies report exhaustion identically (start() === false).
360 if (this.degradedReason) return false;
361
362 // No async crawl to wait on: as soon as the watch set is installed we
363 // have a clean baseline (pendingFiles is only populated by post-start
364 // events). Clear defensively and flip ready.
365 this.pendingFiles.clear();
366 this.ready = true;
367 for (const cb of this.readyWaiters) cb();
368 this.readyWaiters.length = 0;
369 if (IS_TEST_RUNTIME) liveWatchersForTests.set(this.projectRoot, this);
370
371 logDebug('File watcher started', {
372 projectRoot: this.projectRoot,
373 debounceMs: this.debounceMs,
374 mode: this.inertForTests ? 'inert' : supportsRecursiveWatch() ? 'recursive' : 'per-directory',
375 watchedDirs: this.dirWatchers.size || undefined,
376 });
377 return true;
378 } catch (err) {
379 // Watcher setup failed. Watch-resource exhaustion (EMFILE/ENFILE on the
380 // recursive path) is terminal — degrade cleanly with one actionable
381 // warning instead of leaving a half-broken watcher. Everything else
382 // (permission denied, missing directory) keeps the prior quiet-stop.
383 if (isWatchResourceExhaustion(err)) {
384 this.degrade(EXHAUSTION_REASON, { error: String(err) });

Callers

nothing calls this directly

Calls 12

startRecursiveMethod · 0.95
startPerDirectoryMethod · 0.95
degradeMethod · 0.95
stopMethod · 0.95
watchDisabledReasonFunction · 0.90
logDebugFunction · 0.90
buildScopeIgnoreFunction · 0.90
logWarnFunction · 0.90
supportsRecursiveWatchFunction · 0.85
setMethod · 0.80
clearMethod · 0.45

Tested by

no test coverage detected