mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-11-15 03:08:20 +08:00
fix(cli): codegraph status opens the index read-only, and says when a rebuild is running
Status opened the database read-write, so its open ran the writer's
repairs: migrations, the FTS-trigger and secondary-index recovery an
interrupted bulk load leaves behind, and WAL maintenance. During a
`codegraph index`, that recovery recreated the indexes the indexer had
dropped for its bulk load, and the indexer's next write, whose
transaction had already read, failed with SQLITE_BUSY_SNAPSHOT (517):
"Failed to index: database is locked".
Status now opens with `readOnly: true` and leaves every repair to the
writer, as the MCP reader connections already do. When another live
process holds `rebuild.pid`, it says so: a warning in the text output and
`rebuildInProgress` in `--json`, the only new key. `isRebuildInProgress`
is the check `assertNoRebuild` already made.
Added when landing it:
- A read-only open migrates nothing, so status on an index built by
1.4.0 or older (schema 7, no `unresolved_refs.status`) failed with "no
such column: status". The pending-reference count counts every row
when the column is missing, as the v8 migration marks them pending.
- While a rebuild runs, status skips the advice meant for an index at
rest: the interrupted-run references, "run sync", "re-index", and the
oversized-WAL warning, which mid-rebuild all describe the live run.
- Status no longer reclaims a killed session's oversized WAL, so the
warning names the next `codegraph sync` instead of status itself.
- Tests: the issue's exact interleaving (status inside the writer's edge
transaction, as in PR #2916's test), a pre-v8 index, and the
mid-rebuild advice.
Validation (Windows 11, Node 22.22):
- The issue's reproduction with our build of main 69af19ac: status inside
the transaction exits 0, the writer fails with errcode 517 and the five
dropped edge indexes come back. With this change the insert succeeds
and the indexes stay dropped.
- Real rebuilds of excalidraw 95307fa1 (716 files) with `codegraph
status` looping beside them, 3 with default settings and 3 with the
resolution window forced: main failed 6 of 6, each with "store worker:
database is locked" and an empty index; this change failed 0 of 6 over
46 status runs, with identical complete graphs (14,236 nodes, 61,210
edges).
- status-during-rebuild.test.ts: 7/7 pass. With main's sources 6 of 7
fail; without `readOnly` 3 fail, one with "database is locked"; with
the PR as written the pre-v8 and mid-rebuild cases fail.
- Indexes built by v1.6.2 (schema 11) and v1.4.0 (schema 7): the same
numbers as main, and the schema is left as it is. Interrupted-index
warnings (indexing, partial, failed) are unchanged.
Original PR #2921 by @danusha2345.
Fixes #2913
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
committed by
Colby Mchenry
co-authored by
Claude Opus 5.5
parent
5e0c42cb63
commit
0187366697
+32
-7
@@ -1123,7 +1123,17 @@ program
|
||||
}
|
||||
|
||||
const { default: CodeGraph } = await loadCodeGraph();
|
||||
const cg = await CodeGraph.open(projectPath);
|
||||
// Status only reads. Opened read-write, it ran the open-time repairs
|
||||
// meant for the writer: during a rebuild's bulk load it recreated the
|
||||
// secondary indexes the indexer had dropped, and the indexer's next
|
||||
// edge write failed with SQLITE_BUSY_SNAPSHOT, "database is locked"
|
||||
// (#2913). Read-only, it leaves migrations, index repair and WAL
|
||||
// maintenance to the writer.
|
||||
const cg = await CodeGraph.open(projectPath, { readOnly: true });
|
||||
const { isRebuildInProgress } = await import('../mcp/writer-lock');
|
||||
// A `codegraph index` in another process owns the database: the
|
||||
// numbers below are a snapshot of a graph being rebuilt.
|
||||
const rebuildInProgress = isRebuildInProgress(projectPath);
|
||||
const stats = cg.getStats();
|
||||
const changes = cg.getChangedFiles();
|
||||
const backend = cg.getBackend();
|
||||
@@ -1165,6 +1175,9 @@ program
|
||||
worktreeMismatch: worktreeMismatch
|
||||
? { worktreeRoot: worktreeMismatch.worktreeRoot, indexRoot: worktreeMismatch.indexRoot }
|
||||
: null,
|
||||
// True while another process runs `codegraph index` on this
|
||||
// project: the statistics are a snapshot of the rebuild (#2913).
|
||||
rebuildInProgress,
|
||||
index: {
|
||||
builtWithVersion: buildInfo.version,
|
||||
builtWithExtractionVersion: buildInfo.extractionVersion,
|
||||
@@ -1197,14 +1210,18 @@ program
|
||||
if (worktreeMismatch) {
|
||||
warn(worktreeMismatchWarning(worktreeMismatch));
|
||||
}
|
||||
if (indexState === 'indexing') {
|
||||
if (rebuildInProgress) {
|
||||
warn('An index rebuild ("codegraph index") is running in another process — the statistics below are a snapshot of it.');
|
||||
} else if (indexState === 'indexing') {
|
||||
warn('The last index run never finished (killed mid-index?) — the index is truncated. Re-run "codegraph index".');
|
||||
} else if (indexState === 'partial') {
|
||||
warn('The last index run silently dropped files — the index is partial. Re-run "codegraph index".');
|
||||
} else if (indexState === 'failed') {
|
||||
warn('The last index run failed — results may be incomplete. Re-run "codegraph index".');
|
||||
}
|
||||
if (pendingRefs > 0) {
|
||||
// A running rebuild's references are its own work in progress, not an
|
||||
// interrupted run's.
|
||||
if (pendingRefs > 0 && !rebuildInProgress) {
|
||||
warn(`${formatNumber(pendingRefs)} references from an interrupted run are awaiting resolution — some callers/impact edges are missing. Run "codegraph sync" to resolve them.`);
|
||||
}
|
||||
console.log();
|
||||
@@ -1217,15 +1234,16 @@ program
|
||||
console.log(` DB Size: ${(stats.dbSizeBytes / 1024 / 1024).toFixed(2)} MB`);
|
||||
// Surface the WAL sidecar (#1431): a WAL that dwarfs the DB at rest is
|
||||
// the killed-session leak — invisible before this line, it only showed
|
||||
// up as a mysteriously full disk. open() above already kicked off the
|
||||
// automatic heal for the oversized case.
|
||||
// up as a mysteriously full disk. The read-only open above leaves the
|
||||
// heal to the next writer's open, such as a sync (#2913).
|
||||
if (stats.walSizeBytes > 0) {
|
||||
const { WAL_HEAL_THRESHOLD_BYTES } = await import('../db/index');
|
||||
const oversized = stats.walSizeBytes > Math.max(WAL_HEAL_THRESHOLD_BYTES, stats.dbSizeBytes);
|
||||
const walLabel = `${(stats.walSizeBytes / 1024 / 1024).toFixed(2)} MB`;
|
||||
console.log(` WAL Size: ${oversized ? chalk.yellow(walLabel) : walLabel}`);
|
||||
if (oversized) {
|
||||
warn('The write-ahead log is larger than the database — killed sessions left it behind. It is reclaimed automatically on open; if it persists across runs, another live CodeGraph process is holding it.');
|
||||
// A running rebuild's WAL is its own, not a killed session's.
|
||||
if (oversized && !rebuildInProgress) {
|
||||
warn('The write-ahead log is larger than the database — killed sessions left it behind. The next "codegraph sync" reclaims it; if it persists after that, another live CodeGraph process is holding it.');
|
||||
}
|
||||
}
|
||||
// Surface the active SQLite backend (node:sqlite — Node's built-in real
|
||||
@@ -1262,6 +1280,13 @@ program
|
||||
}
|
||||
console.log();
|
||||
|
||||
// What follows is advice for an index at rest: sync its pending changes,
|
||||
// re-index an older build. A running rebuild is replacing this one.
|
||||
if (rebuildInProgress) {
|
||||
cg.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
// Pending changes
|
||||
const totalChanges = changes.added.length + changes.modified.length + changes.removed.length;
|
||||
if (totalChanges > 0) {
|
||||
|
||||
Reference in New Issue
Block a user