fix(mcp,extraction): exit a daemon whose install changed; never store a grammar-load failure (#2335, #2336) (#2346)

* fix(mcp,extraction): a daemon whose install changed exits; grammar-load failures are never stored (#2335, #2336)

A daemon that outlived an upgrade loaded grammars lazily from an install
that no longer existed, and stored every file its watcher re-indexed as a
zero-node row under the file's new content hash: no hash-based sync ever
revisited those rows, and `codegraph status` called the index up to date.

- The daemon checks its own package.json every 30 s
  (CODEGRAPH_DAEMON_INSTALL_CHECK_MS, 0 turns it off) and exits once the
  file is gone or carries another version, so the next session starts a
  daemon from the current install.
- A result whose grammar failed to load (`parser_error`) is never stored:
  the file keeps its previous data and the next sync or index retries it,
  and a row an older engine stored that way is re-indexed even though its
  hash matches. The tag-based CFML path reports a missing grammar with the
  same code.
- A launcher whose own install was deleted no longer crashes on the spawn
  error of the daemon it tried to start.
- `codegraph status` and `status --json` (filesNeedingReindex,
  filesWithParseErrors) report indexed files whose symbols are missing
  although their content is current (#2336), and `files --json` carries
  each file's recorded errors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(mcp): a session whose own install changed never claims the writer lock (#2335)

A daemon now exits once its install is upgraded or removed, and nobody
holds the writer slot for a successor at that point. Its sessions run the
same replaced code and serve themselves in-process the moment it goes; one
that found the slot free claimed it as its writer even though its engine
could not load (a real npm upgrade: "Cannot find module
'./sqlite-adapter'"), and so kept a daemon from the current install from
starting. Such a session now falls back read-only and says to restart it.

Also adds the CHANGELOG entries for the install check and grammar-load
failures (#2335) and for the status report (#2336).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-10-05 15:08:14 +00:00
committed by GitHub
co-authored by Claude Opus 5.5
parent 511d86e94d
commit dea076fd1e
15 changed files with 706 additions and 15 deletions
+48 -1
View File
@@ -1056,6 +1056,33 @@ program
}
});
/**
* The `status` warnings for indexed files whose symbols are missing although
* their content is current (#2335, #2336): one line per group, saying what is
* wrong, what to do, and naming up to three of the files.
*/
function describeFilesMissingSymbols(health: { needsReindex: string[]; parseErrors: string[] }): string[] {
const dash = getGlyphs().dash;
const sample = (paths: string[]): string =>
paths.slice(0, 3).join(', ') + (paths.length > 3 ? ` (+${formatNumber(paths.length - 3)} more)` : '');
const lines: string[] = [];
const reindex = health.needsReindex.length;
if (reindex > 0) {
lines.push(
`${formatNumber(reindex)} ${reindex === 1 ? 'file is missing its' : 'files are missing their'} symbols ${dash} ` +
`run "codegraph sync" to re-index ${reindex === 1 ? 'it' : 'them'}: ${sample(health.needsReindex)}`
);
}
const unparsed = health.parseErrors.length;
if (unparsed > 0) {
lines.push(
`${formatNumber(unparsed)} ${unparsed === 1 ? 'file could not be parsed, so its symbols are' : 'files could not be parsed, so their symbols are'} ` +
`missing: ${sample(health.parseErrors)} ${dash} "codegraph files --json" shows the errors`
);
}
return lines;
}
/**
* codegraph status [path]
*/
@@ -1103,6 +1130,9 @@ program
// Zero on a healthy index; non-zero at rest means a resolution pass was
// interrupted, so some files' call edges are missing (#1187).
const pendingRefs = cg.getPendingReferenceCount();
// Files whose content is current but whose symbols are missing — the
// content-hash check behind "up to date" cannot see them (#2336).
const health = cg.getIndexHealth();
// JSON output mode
if (options.json) {
@@ -1143,6 +1173,12 @@ program
// interrupted resolution pass left edges missing; the next
// sync sweeps them (#1187).
pendingRefs,
// Files stored without their symbols (e.g. while their grammar
// could not load, #2335); the next sync re-indexes them.
filesNeedingReindex: health.needsReindex.length,
// Files with a recorded parse error and no symbols from it
// (#2336); unchanged until the file or the parser changes.
filesWithParseErrors: health.parseErrors.length,
},
}));
cg.destroy();
@@ -1235,7 +1271,10 @@ program
console.log(` Removed: ${changes.removed.length} files`);
}
info('Run "codegraph sync" to update the index');
} else {
}
const missingSymbols = describeFilesMissingSymbols(health);
for (const line of missingSymbols) warn(line);
if (totalChanges === 0 && missingSymbols.length === 0) {
success('Index is up to date');
}
console.log();
@@ -1751,6 +1790,14 @@ program
language: f.language,
nodeCount: f.nodeCount,
size: f.size,
// What extraction recorded for the file — a parse error, a skip
// reason — so a health check needn't read the database (#2336).
errors: (f.errors ?? []).map((e) => ({
severity: e.severity,
code: e.code,
message: e.message,
line: e.line,
})),
}));
console.log(JSON.stringify(output, null, 2));
cg.destroy();