diff --git a/src/javascript/analysis/JavaScriptAnalysisWorker.ts b/src/javascript/analysis/JavaScriptAnalysisWorker.ts index de7ac6c42d..b9471abb96 100644 --- a/src/javascript/analysis/JavaScriptAnalysisWorker.ts +++ b/src/javascript/analysis/JavaScriptAnalysisWorker.ts @@ -278,7 +278,7 @@ export class JavaScriptAnalysisWorker { error: new AnalysisResourceConstraintError( OPERATION, response.resource ?? "memory", - `${file.path}: ${response.message}`, + response.message, { ...this.#limits(), ...response.limits }, ), }); diff --git a/src/javascript/analysis/JavaScriptAnalysisWorkerServer.ts b/src/javascript/analysis/JavaScriptAnalysisWorkerServer.ts index 0e9f15b59a..e3d0a8bc2e 100644 --- a/src/javascript/analysis/JavaScriptAnalysisWorkerServer.ts +++ b/src/javascript/analysis/JavaScriptAnalysisWorkerServer.ts @@ -189,7 +189,13 @@ const analyzeJob = async ( cause instanceof AnalysisResourceConstraintError ? cause.reportedLimits : null, - message: cause instanceof Error ? cause.message : String(cause), + // The parent owns resource-error formatting; send its canonical reason. + message: + cause instanceof AnalysisResourceConstraintError + ? cause.reason + : cause instanceof Error + ? cause.message + : String(cause), }); } }; diff --git a/tests/boundary/javascript/javascriptSourceAnalysisWorker.test.ts b/tests/boundary/javascript/javascriptSourceAnalysisWorker.test.ts index 9abf7ab3d7..9ffe94a94b 100644 --- a/tests/boundary/javascript/javascriptSourceAnalysisWorker.test.ts +++ b/tests/boundary/javascript/javascriptSourceAnalysisWorker.test.ts @@ -277,6 +277,55 @@ catch (error) { } }); +// The real server reports a parent-declined semantic budget through its +// resource frame. +it("formats a real worker's declined semantic budget once", async () => { + const worker = new JavaScriptAnalysisWorker( + { heapMb: 128, timeoutMs: 30_000 }, + async (request) => { + const spawned = await spawnOwnedProviderProcess(request); + const stdin = spawned.process.stdin; + if (stdin === null || stdin === undefined) + throw new Error("Owned analyzer has no stdin"); + const write = stdin.write.bind(stdin) as ( + chunk: string, + callback: (error?: Error | null) => void, + ) => boolean; + stdin.write = (( + chunk: string, + callback: (error?: Error | null) => void, + ) => + write( + chunk.replace( + /"semantic_result_budget_bytes":\d+/u, + '"semantic_result_budget_bytes":0', + ), + callback, + )) as typeof stdin.write; + return spawned; + }, + ); + try { + const result = await worker.analyze( + source("main.js", "export const observed = 7;"), + 100, + ); + if (result.ok) throw new Error("expected a declined semantic budget"); + const { error } = result.error; + expect(error).toMatchObject({ + _tag: "AnalysisResourceConstraintError", + reason: + "The parent reserved its remaining result capacity for static observations; semantic analysis of main.js was not started", + reportedLimits: { semantic_result_budget_bytes: 0 }, + }); + expect(error.message.match(/Resource constraint during/gu)).toHaveLength(1); + expect(error.message.match(/main\.js:/gu)).toBeNull(); + expect(result.error.javascript?.parse_status).toBe("complete"); + } finally { + await worker.close(); + } +}); + // The handshake mirrors the real server: only Node.js reports an enforced V8 heap limit. const resourceFailureWorker = (heapLimit: string) => new JavaScriptAnalysisWorker({ heapMb: 128, timeoutMs: 30_000 }, (request) =>