fix(workers): report every caught parse-worker error over IPC (#2264)

The parse worker swallowed or only-locally-logged several caught errors, so they
never reached the pool: the per-language-group catch was an empty catch {} that
silently dropped the whole group on any throw (not just an unavailable grammar),
the per-file parse/query-execution catches only logger.warn'd (worker-thread
local), and the C++ template-constraint catch swallowed silently.

Route all work-path catches through a new reportWarning() helper that posts
{ type: 'warning', message } to the pool (which logs it on the main thread AND
resets the worker idle timer, so a worker grinding through failing files isn't
falsely idle-evicted), with a logger.warn fallback for the non-worker path. The
existing inline warning sites (query-compilation, the extractParsedFile callback,
CFG build) are migrated to the same helper.

The 4 optional-grammar module-load guards (Swift/Dart/Kotlin/C) stay silent: they
run before the 'ready' handshake and their absence is already surfaced via
result.skippedLanguages + the isLanguageAvailable gate. Fatal/group-aborting
errors continue to flow through the message handler's { type: 'error', errorStack }.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JBJomjoTdBV2eveDVq4JMm
This commit is contained in:
Gergo Magyar 2026-06-21 09:09:18 +00:00
parent e56ddd10c6
commit 18539958e0

View file

@ -966,8 +966,13 @@ const processBatch = (
try {
setLanguage(language, regularFiles[0].path);
processFileGroup(regularFiles, language, queryString, result, onFileProcessed);
} catch {
// parser unavailable — skip this language group
} catch (err) {
// A throw here drops the whole language group — surface it to the pool
// (#2264) instead of silently skipping. The old empty catch hid real
// extractor/parser failures, not just an unavailable grammar.
reportWarning(
`Skipped ${regularFiles.length} ${language} file(s) after a processing error: ${err instanceof Error ? err.message : String(err)}`,
);
}
} else {
result.skippedLanguages[language] =
@ -981,8 +986,12 @@ const processBatch = (
try {
setLanguage(language, tsxFiles[0].path);
processFileGroup(tsxFiles, language, queryString, result, onFileProcessed);
} catch {
// parser unavailable — skip this language group
} catch (err) {
// See above — surface a tsx-group processing failure rather than
// silently dropping every file in it (#2264).
reportWarning(
`Skipped ${tsxFiles.length} ${language} (tsx) file(s) after a processing error: ${err instanceof Error ? err.message : String(err)}`,
);
}
} else {
result.skippedLanguages[language] =
@ -1142,6 +1151,23 @@ export function extractORMQueries(
import { extractFastAPIRouterBindings } from '../route-extractors/fastapi-router-bindings.js';
/**
* Report a non-fatal worker issue to the pool over IPC so a caught error is not
* invisible to the operator (#2264). The pool logs it on the main thread AND
* resets the worker idle timer (so a worker grinding through failing files isn't
* falsely idle-evicted). Falls back to the local logger when there's no parent —
* this code also runs on the main thread in tests / the non-worker path. Fatal,
* group-aborting errors go through the message handler's
* `{ type: 'error', errorStack }` channel instead.
*/
function reportWarning(message: string): void {
if (parentPort) {
parentPort.postMessage({ type: 'warning', message });
} else {
logger.warn(message);
}
}
const processFileGroup = (
files: ParseWorkerInput[],
language: SupportedLanguages,
@ -1154,12 +1180,9 @@ const processFileGroup = (
const lang = parser.getLanguage();
query = new Parser.Query(lang, queryString);
} catch (err) {
const message = `Query compilation failed for ${language}: ${err instanceof Error ? err.message : String(err)}`;
if (parentPort) {
parentPort.postMessage({ type: 'warning', message });
} else {
logger.warn(message);
}
reportWarning(
`Query compilation failed for ${language}: ${err instanceof Error ? err.message : String(err)}`,
);
return;
}
@ -1203,7 +1226,7 @@ const processFileGroup = (
bufferSize: getTreeSitterBufferSize(parseContent),
});
} catch (err) {
logger.warn(
reportWarning(
`Failed to parse file ${file.path}: ${err instanceof Error ? err.message : String(err)}`,
);
continue;
@ -1216,7 +1239,7 @@ const processFileGroup = (
try {
matches = query.matches(tree.rootNode);
} catch (err) {
logger.warn(
reportWarning(
`Query execution failed for ${file.path}: ${err instanceof Error ? err.message : String(err)}`,
);
continue;
@ -1237,13 +1260,7 @@ const processFileGroup = (
provider,
parseContent,
file.path,
(message) => {
if (parentPort) {
parentPort.postMessage({ type: 'warning', message });
} else {
logger.warn(message);
}
},
reportWarning,
tree,
scopeSourceKind,
);
@ -1306,9 +1323,9 @@ const processFileGroup = (
};
}
} catch (err) {
const message = `CFG build failed for ${file.path}: ${err instanceof Error ? err.message : String(err)}`;
if (parentPort) parentPort.postMessage({ type: 'warning', message });
else logger.warn(message);
reportWarning(
`CFG build failed for ${file.path}: ${err instanceof Error ? err.message : String(err)}`,
);
}
}
@ -2109,7 +2126,12 @@ const processFileGroup = (
if (parsedTemplateConstraints !== undefined) {
constraintsTag = templateConstraintsIdTag(parsedTemplateConstraints);
}
} catch {
} catch (err) {
// Optional C++ template-constraint enrichment: fall back to no tag, but
// surface the failure (#2264) — matches the CFG-build warning above.
reportWarning(
`Template-constraint extraction failed for ${file.path}: ${err instanceof Error ? err.message : String(err)}`,
);
parsedTemplateConstraints = undefined;
constraintsTag = '';
}