fix(browser): report Windows HAR heap exhaustion (#1147)

Recognize exit code 134 without a signal when the HAR decoder reports
V8 heap exhaustion. Preserve the observed termination in the memory
failure and cover Windows, POSIX, and unrelated decoder failures.
This commit is contained in:
Andrew Barnes authored and GitHub committed 2026-10-09 09:13:00 +08:00
1 parent 441813c070
commit 13935077ba
3 files changed
+85 -3

No files matched your search

@@ -7,6 +7,83 @@ import { historicalCaptureFailure } from "./CaptureFailures.js";
import { redactExplicitFailure } from "../../domain/explicitSensitiveFailure.js";
import { OwnedCommandFailure } from "../../process/OwnedCommand.js";
it.each([
{ label: "POSIX SIGABRT", exitCode: null, signal: "SIGABRT" },
{ label: "Windows exit 134", exitCode: 134, signal: null },
] as const)(
"reports HAR heap exhaustion for $label",
({ exitCode, signal }) => {
const input = inspectWebNetworkCaptureInputSchema.parse({
capture_path: "/selected/input.har",
format: "har",
});
const stderr =
"FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory\r\n";
const cause = new OwnedCommandFailure("process", "Decoder failed", {
stdout: { text: "", bytes: 0 },
stderr: { text: stderr, bytes: Buffer.byteLength(stderr) },
exitCode,
signal,
});
const projected = analysisErrorProjectionSchema.parse(
projectAnalysisError(historicalCaptureFailure(input, cause, "decoder")),
);
expect(projected).toMatchObject({
code: "resource_constraint",
retryable: false,
message: expect.stringContaining("fixed V8 heap"),
remediation: {
action: expect.stringContaining(
"smaller capture exported by its producer",
),
},
details: {
resource: "memory",
reported_limits: {
observed_exit_code: exitCode,
observed_signal: signal,
},
captured_output: { stdout: "", stderr },
},
});
expect(JSON.stringify(projected)).not.toContain("rea doctor");
},
);
it.each([
{ exitCode: 134, signal: null, stderr: "Fatal decoder crash" },
{
exitCode: 2,
signal: null,
stderr: "FATAL ERROR: JavaScript heap out of memory",
},
{
exitCode: null,
signal: "SIGTERM",
stderr: "FATAL ERROR: JavaScript heap out of memory",
},
] as const)(
"preserves unrelated decoder failures %#",
({ exitCode, signal, stderr }) => {
const input = inspectWebNetworkCaptureInputSchema.parse({
capture_path: "/selected/input.har",
format: "har",
});
const cause = new OwnedCommandFailure("process", "Decoder failed", {
stdout: { text: "", bytes: 0 },
stderr: { text: stderr, bytes: Buffer.byteLength(stderr) },
exitCode,
signal,
});
expect(
projectAnalysisError(historicalCaptureFailure(input, cause, "decoder")),
).toMatchObject({
code: "execution_failure",
details: { diagnostics: { exit_code: exitCode, signal, stderr } },
});
},
);
it.each(["before open", "during read"])(
"preserves observed capture changes %s as retryable acquisition integrity failures",
(stage) => {
+6 -2
View File
@@ -61,7 +61,8 @@ export const historicalCaptureFailure = (
phase === "decoder" &&
input.format === "har" &&
cause.reason === "process" &&
cause.snapshot?.signal === "SIGABRT" &&
(cause.snapshot?.signal === "SIGABRT" ||
(cause.snapshot?.signal === null && cause.snapshot.exitCode === 134)) &&
/FATAL ERROR:[^\r\n]*heap out of memory/u.test(cause.snapshot.stderr.text)
)
return captureMemoryFailure(
@@ -182,7 +183,10 @@ export const captureMemoryFailure = (
}
: {}),
...(cause instanceof OwnedCommandFailure
? { observed_signal: cause.snapshot?.signal ?? null }
? {
observed_exit_code: cause.snapshot?.exitCode ?? null,
observed_signal: cause.snapshot?.signal ?? null,
}
: {}),
},
{
@@ -70,7 +70,8 @@ it("reports real HAR heap exhaustion through CLI and MCP and preserves server us
resource: "memory",
reported_limits: {
old_generation_heap_mib: 192,
observed_signal: "SIGABRT",
observed_exit_code: process.platform === "win32" ? 134 : null,
observed_signal: process.platform === "win32" ? null : "SIGABRT",
},
captured_output: {
stderr: expect.stringContaining("heap out of memory"),