Files
codegraph/__tests__
Colby MchenryandClaude Opus 5.5 8073637d66 fix(db): reopening an interrupted index no longer gets the server killed at every start (#2794)
Root cause: a run killed inside a bulk-load window (a crash, an OOM kill,
the liveness watchdog itself) leaves that window's secondary indexes
dropped, and since PR #1562 every writable open rebuilds them in
DatabaseConnection.open(), one CREATE INDEX after another on the caller's
thread. Each one scans and sorts its whole table. On a large index the sort
writes nothing to the database or its WAL, so on an MCP server the liveness
watchdog saw a silent main thread with no disk progress, and killed the
process before the index committed. Each restart died at the same index,
and the proxy's in-process fallback died the same way on the request it
re-served.

Fix: an open on a watched event loop leaves the rebuild to a worker thread
with its own connection, and the project is served once the rebuild is
done.
- DatabaseConnection.open({ deferIndexRecovery }) records the missing DDL
  instead of running it; recoverIndexesOffThread() runs it on a worker
  (single-flight, rejects on failure so the next call retries; SQLite's
  default temp store, so the sort spills to disk; the worker collects its
  garbage and exits by itself, per worker-teardown.ts).
- CodeGraph.open() (the MCP default project, the CLI) defers and awaits it.
- openSync({ deferIndexRecovery }) plus cg.recoverIndexes() for the MCP's
  synchronous opens: the ToolHandler's projectPath projects and the engine's
  retry path. ToolHandler.execute awaits the rebuild before serving (not
  time-boxed: reads without the indexes scan whole tables), the project
  lifecycle awaits it before its catch-up sync writes, and the retry path
  starts the query pool after it.
- Synchronous opens that ask for nothing keep rebuilding in line, as before.

Validation:
- Reproduced on main b2a84873: a django 79502d0 init killed 1.5s into the
  resolution window left the field report's index set missing (5 ref + 5
  edge indexes, index_state indexing); the in-line rebuild is about 750ms
  of main-thread work there.
- Real daemon on Windows, a 3M-ref / 1M-edge index left the same way
  (781 MB), watchdog 1.5s: main was killed in 4 of 4 starts, stuck at 2 of
  10 indexes, the launcher's in-process fallback killed too, no reply. The
  fix rebuilt all 10 in about 11s with no main-thread block over 506ms,
  answered the call, and the next start opened in about 120ms.
- New __tests__/index-recovery-off-thread.test.ts (9 cases): main's source
  8 fail / 1 pass (the in-line guard), fix 9 pass. Spies show no CREATE
  INDEX on the server thread for CodeGraph.open, a projectPath call
  (watched and not), the retry path and the startup path; a real watchdog
  (500ms) kills main mid-rebuild of a 300k-ref index and leaves the fix
  alone. 6 mutations each failed at least one case.
- Contributor PR #1888 and the stale draft PR #1919 were read for guidance;
  this ports their worker recovery to current main's open paths.

Fixes #1887

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 09:44:58 +00:00
..