Follow-up to #1065/#1066. Those stopped a *new* index from scanning an ignored gitlink corpus, but a project that had already built the multi-GB graph before upgrading still couldn't recover: `codegraph index` printed only "Indexing project" and was then SIGKILLed (137) by the #850 watchdog ~60s later, before scanning even started. Root cause is not the scanner. `index` cleared the old graph with a synchronous `DELETE FROM nodes/edges/files`. `nodes` carries an FTS5 `AFTER DELETE` trigger, so deleting ~1.6M rows fires ~1.6M FTS delete-markers — O(rows), and it grows the WAL further before it can finish. A deterministic probe puts the DELETE-clear at 20.4s on 1.5M synthetic nodes (WAL 1.16->2.14GB); at the report's denser ~2.6KB/node WAL that crosses the 60s main-thread watchdog. `open()` was never the wedge. A full re-index is documented as "same result as a fresh init", so make it one: discard the database files and re-initialize, instead of opening the old DB and DELETE-ing every row. - db: add removeDatabaseFiles(dbPath) — unlinks codegraph.db + its -wal/-shm sidecars (O(1) regardless of size; sidecars best-effort). - index: add CodeGraph.recreate(projectRoot) — discards the files and returns a fresh, empty instance. Never opens or migrates the poisoned DB. POSIX unlinks an open file fine (a live daemon heals via reopenIfReplaced, #925); a Windows file lock becomes an actionable "stop the daemon / remove .codegraph" error. - cli: `codegraph index` now calls recreate() instead of open()+clear(); both clear() calls dropped. The public clear() API is unchanged. This also reclaims the disk the bloated db/-wal were holding. Validated: deterministic probe (DELETE O(rows) vs recreate O(1)); an end-to-end run through the built binary recovering a real 800K-node / 419MB poisoned DB in 0.3s with no wedge and the correct small graph; new unit + CLI regression tests; existing #874 index tests still green. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
7e3da77f21
commit
9684b3b5a5
+49
-1
@@ -22,7 +22,7 @@ import {
|
||||
BuildContextOptions,
|
||||
FindRelevantContextOptions,
|
||||
} from './types';
|
||||
import { DatabaseConnection, getDatabasePath } from './db';
|
||||
import { DatabaseConnection, getDatabasePath, removeDatabaseFiles } from './db';
|
||||
import { QueryBuilder } from './db/queries';
|
||||
import {
|
||||
isInitialized,
|
||||
@@ -319,6 +319,54 @@ export class CodeGraph {
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild the project's database from scratch and return a fresh, empty
|
||||
* instance — the "same result as a fresh init" semantics that `codegraph
|
||||
* index` documents.
|
||||
*
|
||||
* Unlike `open()` followed by `clear()`, this DISCARDS the existing
|
||||
* `.codegraph/codegraph.db` (and its `-wal`/`-shm` sidecars) before
|
||||
* re-initializing, instead of opening the old database and DELETE-ing every
|
||||
* row. On a large or pre-fix poisoned index — e.g. an old graph that scanned
|
||||
* an ignored gitlink corpus (#1065) into ~1.6M nodes with a multi-GB WAL —
|
||||
* the per-row `nodes_fts` delete-trigger churn blocks the main thread long
|
||||
* enough to trip the #850 liveness watchdog before indexing even starts, so a
|
||||
* full re-index could never recover the bad state (#1067). Discarding the
|
||||
* files is O(1) regardless of size, reclaims the disk, and sidesteps opening
|
||||
* (and running migrations against) the poisoned database entirely.
|
||||
*/
|
||||
static async recreate(projectRoot: string): Promise<CodeGraph> {
|
||||
await initGrammars();
|
||||
const resolvedRoot = path.resolve(projectRoot);
|
||||
|
||||
// Check if initialized — recreate REBUILDS an existing project; it is not a
|
||||
// first-time `init`.
|
||||
if (!isInitialized(resolvedRoot)) {
|
||||
throw new Error(`CodeGraph not initialized in ${resolvedRoot}. Run init() first.`);
|
||||
}
|
||||
|
||||
const dbPath = getDatabasePath(resolvedRoot);
|
||||
try {
|
||||
removeDatabaseFiles(dbPath);
|
||||
} catch (err) {
|
||||
// POSIX unlinks an open file fine; this fires mainly on Windows when a
|
||||
// live daemon/MCP server still holds the database. Turn the raw EBUSY into
|
||||
// an actionable instruction instead of a generic failure.
|
||||
const reason = err instanceof Error ? err.message : String(err);
|
||||
throw new Error(
|
||||
`Could not rebuild the index — the database file is in use (${reason}). ` +
|
||||
`Stop any running CodeGraph MCP server/daemon for this project and retry, ` +
|
||||
`or remove the ${getCodeGraphDir(resolvedRoot)} directory and run "codegraph init".`
|
||||
);
|
||||
}
|
||||
|
||||
// Re-create an empty, freshly-schema'd database at the same path.
|
||||
const db = DatabaseConnection.initialize(dbPath);
|
||||
const queries = new QueryBuilder(db.getDb());
|
||||
|
||||
return new CodeGraph(db, queries, resolvedRoot);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open synchronously (without sync)
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user