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.
This commit is contained in:
Colby Mchenry
2026-06-13 12:59:41 -05:00
committed by GitHub
parent 2472508549
commit 0f825649a1
5 changed files with 375 additions and 1 deletions
+6
View File
@@ -54,6 +54,7 @@ import {
getDaemonSocketPath,
} from './daemon-paths';
import { CodeGraphPackageVersion } from './version';
import { registerDaemon, deregisterDaemon } from './daemon-registry';
/** Default idle linger after the last client disconnects. */
const DEFAULT_IDLE_TIMEOUT_MS = 300_000;
@@ -191,6 +192,10 @@ export class Daemon {
startedAt: Date.now(),
};
// Drop a discovery record so `codegraph list` / `stop --all` can find us.
// Best-effort; a missing record only means list's liveness prune covers it.
registerDaemon({ root: this.projectRoot, ...lock });
process.stderr.write(
`[CodeGraph daemon] Listening on ${this.socketPath} (pid ${process.pid}, v${CodeGraphPackageVersion}). Idle timeout ${this.idleTimeoutMs}ms.\n`
);
@@ -244,6 +249,7 @@ export class Daemon {
}
this.engine.stop();
this.cleanupLockfile();
deregisterDaemon(this.projectRoot);
if (process.platform !== 'win32') {
try { fs.unlinkSync(this.socketPath); } catch { /* may already be gone */ }
}