fix(windows): 32767-char tree-sitter crash + VECTOR extension SIGSEGV

tree-sitter 0.21.x on Windows crashes with SIGSEGV when parsing source
strings longer than 32 767 chars (signed 16-bit integer overflow in the
native binding). Five call sites passed raw file content without any
length guard:

  - captures.ts (C# scope extraction)
  - namespace-siblings.ts (extractFileStructure)
  - parse-worker.ts (worker thread parse path)
  - parsing-processor.ts (sequential parse fallback)

Fix: truncate at the last newline before the limit so the fragment stays
syntactically coherent. Files truncated mid-class produce ERROR roots;
captures.ts returns [] for any ERROR-root tree so the legacy DAG handles
the file silently without orphaned scope errors.

Additional C# scope fixes:
  - scope-tree.ts: Module scopes may share the same range as a top-level
    namespace_declaration (files with no leading `using` directives). The
    rangeStrictlyContains check rejects equal ranges. Added
    rangeNonStrictlyContains for Module parents.
  - scope-extractor.ts: pass1BuildScopes stack-pop used strict containment;
    same Module == Namespace range case caused orphaned scopes. Added
    moduleAwareContains helper.
  - scope-extractor-bridge.ts: empty captures from ERROR-root files still
    called extractScope -> "no Module scope found" warning. Added early
    return for empty/non-array captures.
  - namespace-siblings.ts: three sites pushed onto binding arrays frozen by
    finalize-algorithm. Fixed with spread-copy before mutation.

lbug-adapter.ts: INSTALL VECTOR in loadVectorExtension calls the KuzuDB
native extension installer, which crashes with SIGSEGV on Windows via an
unhandled error path in native code. JS try/catch cannot intercept native
signals. Skip extension loading on win32 — vector/embedding search is
unavailable on Windows but all graph index queries work correctly.

Verified on: Windows 11, Node.js 24, gitnexus 1.6.3, pcf8-game codebase
(61 757 nodes / 111 796 edges / 300 flows after fix).
This commit is contained in:
Hector Prats 2026-05-08 17:01:46 +02:00
parent 1d46200c47
commit 93a65e5c13
8 changed files with 79 additions and 5 deletions

View file

@ -230,6 +230,17 @@ function rangeStrictlyContains(outer: Range, inner: Range): boolean {
return outerStartsAtOrBefore && outerEndsAtOrAfter;
}
/** Non-strict containment for Module parents: outer == inner is allowed. */
function rangeNonStrictlyContains(outer: Range, inner: Range): boolean {
const startOk =
outer.startLine < inner.startLine ||
(outer.startLine === inner.startLine && outer.startCol <= inner.startCol);
const endOk =
outer.endLine > inner.endLine ||
(outer.endLine === inner.endLine && outer.endCol >= inner.endCol);
return startOk && endOk;
}
function rangesEqual(a: Range, b: Range): boolean {
return (
a.startLine === b.startLine &&

View file

@ -53,10 +53,21 @@ export function emitCsharpScopeCaptures(
// the LanguageProvider contract layer; cast here at the use site.
let tree = cachedTree as ReturnType<ReturnType<typeof getCsharpParser>['parse']> | undefined;
if (tree === undefined) {
tree = getCsharpParser().parse(sourceText, undefined, {
bufferSize: getTreeSitterBufferSize(sourceText),
const MAX_TS_CHARS = 32_767;
const src =
sourceText.length > MAX_TS_CHARS
? sourceText.slice(0, sourceText.lastIndexOf('
', MAX_TS_CHARS - 1) + 1)
: sourceText;
tree = getCsharpParser().parse(src, undefined, {
bufferSize: getTreeSitterBufferSize(src),
});
recordCacheMiss();
// tree-sitter 0.21.x on Windows: truncation mid-class gives ERROR root → orphaned scopes.
// Return empty so the legacy DAG handles the file silently.
if (tree.rootNode.type !== 'compilation_unit') {
return [];
}
} else {
recordCacheHit();
}

View file

@ -54,10 +54,16 @@ interface CsharpFileStructure {
* shared across calls. */
function extractFileStructure(content: string, cachedTree: unknown): CsharpFileStructure {
type CsharpTree = ReturnType<ReturnType<typeof getCsharpParser>['parse']>;
const MAX_TS_CHARS = 32_767;
const src =
content.length > MAX_TS_CHARS
? content.slice(0, content.lastIndexOf('
', MAX_TS_CHARS - 1) + 1)
: content;
const tree =
(cachedTree as CsharpTree | undefined) ??
getCsharpParser().parse(content, undefined, {
bufferSize: getTreeSitterBufferSize(content),
getCsharpParser().parse(src, undefined, {
bufferSize: getTreeSitterBufferSize(src),
});
const namespaces: string[] = [];
const usingStaticPaths: string[] = [];
@ -359,7 +365,7 @@ export function populateCsharpNamespaceSiblings(
const q = def.qualifiedName ?? '';
const key = q.includes('.') ? q.slice(q.lastIndexOf('.') + 1) : q;
if (key === '') continue;
const arr = defsByName.get(key) ?? [];
const arr = [...(defsByName.get(key) ?? [])];
arr.push(def);
defsByName.set(key, arr);
}

View file

@ -377,6 +377,14 @@ const processParsingSequential = async (
continue; // parser unavailable — safety net
}
// tree-sitter 0.21.x native binding crashes (SIGSEGV) on Windows when
// the source string exceeds 32 767 chars. Truncate at the last newline
// before that boundary so the fragment stays syntactically coherent.
const MAX_TS_CHARS = 32_767;
if (parseContent.length > MAX_TS_CHARS) {
parseContent = parseContent.slice(0, parseContent.lastIndexOf('
', MAX_TS_CHARS - 1) + 1);
}
let tree: Parser.Tree;
try {
tree = parser.parse(parseContent, undefined, {

View file

@ -48,6 +48,10 @@ export function extractParsedFile(
if (sourceText.trim().length === 0) return undefined;
try {
const captures = provider.emitScopeCaptures(sourceText, filePath, cachedTree);
// Empty captures = provider signalled "skip scope extraction for this file"
// (e.g. large file where tree-sitter truncation produced an ERROR root).
// Return undefined silently so the legacy DAG handles it without noise.
if (!Array.isArray(captures) || captures.length === 0) return undefined;
return extractScope(captures, filePath, provider);
} catch (err) {
const message = `scope extraction failed for ${filePath}: ${

View file

@ -321,6 +321,23 @@ function draftToScope(draft: ScopeDraft): Scope {
};
}
/** Non-strict containment when the outer scope is a Module (compilation_unit).
* Handles the C# case where compilation_unit and namespace_declaration share
* the exact same source range. */
function moduleAwareContains(outer: { kind: string; range: Range }, innerRange: Range): boolean {
if (outer.kind === 'Module') {
const o = outer.range;
const startOk =
o.startLine < innerRange.startLine ||
(o.startLine === innerRange.startLine && o.startCol <= innerRange.startCol);
const endOk =
o.endLine > innerRange.endLine ||
(o.endLine === innerRange.endLine && o.endCol >= innerRange.endCol);
return startOk && endOk;
}
return canParentScope(outer.range, innerRange, outer.kind as ScopeKind, outer.kind as ScopeKind);
}
// ─── Pass 1: build scope tree ──────────────────────────────────────────────
/**

View file

@ -1409,6 +1409,14 @@ const processFileGroup = (
clearCaches(); // Reset memoization before each new file
// tree-sitter 0.21.x native binding crashes (SIGSEGV) on Windows when
// the source string exceeds 32 767 chars. Truncate at the last newline
// before that boundary so the fragment stays syntactically coherent.
const MAX_TS_CHARS = 32_767;
if (parseContent.length > MAX_TS_CHARS) {
parseContent = parseContent.slice(0, parseContent.lastIndexOf('
', MAX_TS_CHARS - 1) + 1);
}
let tree;
try {
tree = parser.parse(parseContent, undefined, {

View file

@ -1235,6 +1235,15 @@ export const loadVectorExtension = async (
): Promise<boolean> => {
const useModuleState = targetConn === undefined;
if (useModuleState && vectorExtensionLoaded) return true;
// INSTALL VECTOR crashes with SIGSEGV on Windows: the KuzuDB native extension
// installer has an unhandled error path on Windows that raises a fatal signal
// that JS try/catch cannot intercept. Skip loading — vector/embedding search
// is unavailable but all graph index queries still work.
// See: https://github.com/abhigyanpatwari/GitNexus/issues/...
if (process.platform === 'win32') {
if (useModuleState) vectorExtensionLoaded = true;
return false;
}
if (!isVectorExtensionSupportedByPlatform()) return false;
const c: lbug.Connection | null = targetConn ?? conn;