5 Commits
Author SHA1 Message Date
Colby MchenryandClaude Opus 5.5 3f58e34383 fix(cli): index no longer blocks on a daemon lock naming a process that isn't CodeGraph (#2819)
codegraph index told you to run `codegraph daemon stop`, which the CLI
rejects ("too many arguments"), whenever it could not verify the process
named in .codegraph/daemon.pid. In that state `codegraph daemon` lists
nothing, because it lists only daemons that answer their socket. And
`codegraph unlock` kept an old-format lock whose process was alive (the
rule from issue #1834), so nothing the CLI offered got past it. The
writer-lock message gave the same wrong advice.

Root cause: a live pid in the lock was the only evidence, and a pid
proves nothing once the OS gives it to another program. Every CodeGraph
daemon since the first one (v0.9.5) runs
`<runtime> ... codegraph.js serve --mcp --path <root>`, so the holder's
command line settles the question.

Fix:
- New src/mcp/process-identity.ts reads a process's command line and
  executable name from the OS: Win32_Process through PowerShell (tasklist
  as fallback) on Windows, /proc on Linux, ps elsewhere. Each lookup is
  capped at 5 s and never throws. A holder is a CodeGraph MCP server,
  provably something else, or unknown, and unknown is never evidence.
- stopDaemonAt treats an unverified live holder that is provably not a
  CodeGraph server like a dead one. Its lock is cleared, under the same
  writer-slot claim, and the process is never signalled.
  clearStaleDaemonArtifacts (codegraph unlock) does the same for an
  old-format lock. A holder that may be a daemon keeps its lock, as
  before (issues #1553 and #1834).
- daemonStopFailureMessage builds index's error from what remains: the
  pid, what it runs, and a command that clears it (taskkill /PID N /F or
  kill N), or which lock file to delete when only that will do.
- writerLockHeldMessage names `codegraph daemon` instead of the missing
  `codegraph daemon stop`.

Validation (Windows 11):
- New daemon-lock-holder.test.ts (11 tests) and a daemon-registry test.
  With main's daemon-registry.ts, writer-lock.ts and codegraph.ts put
  back, 11 tests fail, including all 8 CLI cases and the writer-lock
  wording; with the fix all pass. Four existing tests used generic
  processes to stand in for a daemon that doesn't answer. Those stand-ins
  now carry a CodeGraph server's command line, and pass on main and here.
- Real CLI with a private HOME, 4 cases: an old-format lock naming a live
  PING.EXE; an old-format lock naming a live Node process that isn't
  CodeGraph; a current-format lock naming that kind of Node process with
  a pipe nobody listens on; an old-format lock naming a Node process whose
  command line reads `codegraph.js serve --mcp`. On main all 4 index runs
  exit 1 with the old advice, `daemon stop` is rejected, `daemon` lists
  nothing, and unlock clears only the third. With the fix the first 3
  rebuild (exit 0), each holder stays alive, and unlock clears the first
  too. The 4th exits 1, naming the pid and `taskkill /PID <pid> /F`;
  after that command, index exits 0.

Contributor PR #2373 (@danusha2345) changes the two strings only; I read
it for guidance.

Fixes #2770

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 23:32:31 +00:00
Colby MchenryandAaron Queen 594bad7ec4 test(writer-lock): hold the lock with a live child, not a pid assumed alive (#2045)
The live-holder case wrote `pid: 1` and leaned on its own comment, "On Linux,
PID 1 is almost always alive". Windows has no PID 1, so tryAcquireWriterLock
read the holder as dead and acquired — the correct behaviour for a dead
holder, and a failing assertion. The platform-specific part was the fixture,
not the lock.

A parked child process is alive on every platform, and not stealing from a
live holder is what the lock actually promises, so the case now tests the
promise rather than a number that happens to name a running process. The child
is reaped in afterEach.

Fixes #1752.

(cherry picked from commit bee140288d)

Co-authored-by: Aaron Queen <bompus@users.noreply.github.com>
2026-09-27 15:01:02 +00:00
585f37066c fix(mcp): keep explicit projects synchronized (#1835) (#2036)
* fix(mcp): keep an explicit projectPath project in sync (#1835)

A project opened through a tool call's `projectPath` (a repository other
than the server's default — e.g. an indexed child of an un-indexed
workspace) was opened read-only: no catch-up sync on open and no file
watcher, so its answers went stale until someone ran `codegraph sync`.

The engine now owns the lifecycle of every project the ToolHandler opens
for an explicit path, mirroring the default project:
- the project's writer lock is acquired (#1740 single-writer rule); if
  another live process holds it, that process keeps syncing and we only
  read;
- a catch-up `sync()` runs on open and the first call against that
  project awaits it, time-boxed like the default gate (#905);
- a file watcher runs for as long as the project stays cached.

Bounds: the cache is keyed by the canonical (realpath) root, so a
symlinked spelling shares one connection and one watcher; it is LRU with
`MAX_CACHED_PROJECTS = 8` — opening a ninth closes the least recently
used (watcher stopped, writer lock released, DB closed); `stop()` closes
them all. Un-indexed paths still get the success-shaped guidance.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(mcp): keep explicit projects synchronized (#1835)

Explicit projectPath connections bypassed catch-up and synchronization ownership.
Share canonical project leases and catch-up gates, retain daemon sessions, and retry ownership after a writer exits.
Advertise direct-writer readiness and drain active work before eviction or shutdown.
Add exact SQLite regressions for concurrent sessions, aliases, no-watch behavior, and teardown.

Co-authored-by: danusha2345 <danusha2345@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(mcp): retry catch-up quietly on sync lock contention

Handle LockUnavailableError without marking catch-up complete or logging an alarming failure, and verify timer-driven recovery against a real indexing lock.

Make the eviction regression deterministic by applying its short timeout only to the held catch-up. Other projects must finish reconciling before the test can assert that the oldest eligible graph was closed.

---------

Co-authored-by: danusha2345 <danusha2345@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-27 13:46:58 +00:00
Christopher BeaulieuandColby Mchenry 1e46123758 fix(daemon): preserve live legacy locks (#1834) (#1850)
Co-authored-by: Colby Mchenry <me@colbymchenry.com>
2026-09-15 16:36:00 +00:00
Colby MchenryandColby McHenry 7440d2c475 fix(mcp): fail fast on a second direct-mode writer per project (#1740) (#1744)
Concurrent CODEGRAPH_NO_DAEMON / in-process fallback serve --mcp instances
each started a FileWatcher and contended on codegraph.lock until auto-sync
degraded. Add an exclusive .codegraph/writer.pid lock held by the daemon or
the single direct writer; a second writer exits with actionable guidance.
Daemon mode still multiplexes N proxies onto one writer.

Co-authored-by: Colby McHenry <colbymchenry@users.noreply.github.com>
2026-09-07 23:56:52 -05:00