Commit Graph
5 Commits
Author SHA1 Message Date
Colby MchenryandGitHub 0f825649a1 feat(cli): codegraph stop / list to manage background daemons (#861)
Adds first-class daemon control (the #845 pain point: no clean way to stop a
runaway daemon). `codegraph stop [path]` stops the current/given project's
daemon (SIGTERM -> SIGKILL fallback, sweeps artifacts); `stop --all` stops every
daemon; `list`/`ps` shows running daemons (--json for scripts).

Discovery via a small self-healing registry: each daemon records its root under
~/.codegraph/daemons/ on start, removes it on graceful shutdown; readers prune
dead pids. Cross-platform by construction (files + process.kill). Validated live
on macOS, Linux (docker), and Windows (VM): registry unit 6/6 and real-daemon
stop/list 6/6 on each.
2026-06-13 12:59:41 -05:00
80358a84d9 fix(daemon): reap dead-peer clients + inactivity backstop so a daemon can't leak (#692) (#712)
Layer-2 defense-in-depth follow-up to the Windows PPID watchdog fix (#711).
That fix makes an orphaned proxy exit so its socket closes and the daemon
reaps via the refcount + idle timer. This adds two daemon-side safety nets for
the residual case where a socket close is never delivered (a Windows named-pipe
hazard) and a phantom client would otherwise pin the daemon forever:

  - Liveness sweep: a proxy now sends an optional client-hello carrying its pid
    (+ host pid) right after verifying the daemon hello; the daemon periodically
    drops any client whose peer process is dead, re-arming the idle timer.
    Fail-safe and version-pinned — a connection that never sends the hello just
    falls back to the socket-close lifecycle, and the daemon reads it before the
    transport so a non-hello first line is handed through untouched.
  - Inactivity backstop: the daemon exits after a generous no-traffic window
    (CODEGRAPH_DAEMON_MAX_IDLE_MS, default 30 min) even with clients attached, so
    a phantom client that sends nothing can't keep it alive.

Pure helpers (parseClientHelloLine, peerIsDead) are unit-tested; the full
handshake + sweep and the backstop are covered end-to-end in mcp-daemon.test.ts.
Validated on a real Windows 11 VM: the sweep reaps a dead-pid client over a
named pipe and the backstop fires with a client still connected.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 16:23:48 -04:00
c9559d9991 fix(watcher): bound fd/watch cost with a native fs.watch hybrid (#644, #496, #555, #628, #579) (#650)
chokidar v4 holds one OS file descriptor per watched file on macOS (libuv's
kqueue backend registers an fd per vnode; fsevents is installed but v4 no
longer uses it). On a large project the `serve --mcp` daemon accumulated tens
of thousands of open REG descriptors and exhausted kern.maxfiles — crashing
unrelated processes system-wide with ENFILE. #276 only trimmed the count by
ignoring directories; the source tree still cost one fd per file.

Replace chokidar with a pure-JS native fs.watch hybrid, keeping codegraph's
zero-native-addon "any OS builds any bundle" invariant:

  - macOS / Windows: a single recursive fs.watch (one FSEvents stream /
    ReadDirectoryChangesW handle) -> O(1) descriptors regardless of repo size.
  - Linux: one inotify watch per directory (O(dirs), dynamic add for new
    dirs, capped via CODEGRAPH_MAX_DIR_WATCHES) instead of per-file watches.

Validated empirically: macOS 0 extra fds at 6k and 12k files; Linux 31 inotify
watches at 6k files (per-file would be 6k); Windows recursive catches nested
and new-directory edits. Full test suite green.

Tests drive the watcher through an inertForTests seam (no OS watcher) for
determinism under parallel vitest, with one real-fs end-to-end test exercising
the genuine native path.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 14:21:29 -05:00
b09b23cf54 fix(watcher): exclude ignored dirs before watching to prevent inotify exhaustion (#276)
The file watcher registered a recursive watch over the entire project (node_modules, build output, caches included) and filtered only in the callback — exhausting the Linux inotify budget on large repos (#276). It now uses chokidar and excludes the same directories the indexer ignores (built-in default-ignore set + the project .gitignore) BEFORE registering a watch, so the watch count on a 900-dir node_modules drops from ~1200 to ~14 even with no .gitignore. Stacks with the shared daemon (#411): one watcher across agents, now small.

Also hardens the #411 daemon lockfile against a concurrent-startup race the new watcher timing made reproducible — the lock is now created atomically with its content (temp-write + hard-link), so racing daemons can never both win. Validated on macOS, Linux (Docker), and Windows (chokidar + fs.linkSync on NTFS).

Co-Authored-By: Colby McHenry <me@colbymchenry.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 20:12:44 -05:00
995da54430 feat(mcp): share one serve --mcp per project across MCP clients (#411)
One shared, detached daemon per project root: every `codegraph serve --mcp` is a thin stdio<->socket proxy (Unix socket / Windows named pipe) to it, so N agents in one repo share a single file watcher, SQLite connection, and tree-sitter warm-up instead of N copies. The daemon outlives any single session and reaps via client-refcount + idle timeout; `CODEGRAPH_NO_DAEMON=1` opts out.

Hardened during review: detached-process lifecycle (preserves the #277 watchdog via the proxy; the daemon no longer orphans on host SIGKILL), atomic lockfile + pid-verified stale-clear (no double-daemon on concurrent startup), realpath root canonicalization. Validated on macOS, Linux (Docker - 3x fewer inotify watches for 3 agents), and Windows (named pipes); A/B confirms byte-identical tool output vs direct mode. Closes #411.

Co-Authored-By: Colby McHenry <me@colbymchenry.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 19:54:56 -05:00