From 5e8b2143eac8a5d118632ecf636f7697ff0947d8 Mon Sep 17 00:00:00 2001 From: "paseo-bot[bot]" <266920839+paseo-bot[bot]@users.noreply.github.com> Date: Fri, 9 Oct 2026 22:04:06 +0200 Subject: [PATCH] test(server): reproduce heap exhaustion in overlapping directory searches --- ...ctory-suggestions-memory.local.e2e.test.ts | 55 +++++++++++++++++++ scripts/repro-directory-search-memory.py | 25 +++++++++ 2 files changed, 80 insertions(+) create mode 100644 packages/server/src/utils/directory-suggestions-memory.local.e2e.test.ts create mode 100644 scripts/repro-directory-search-memory.py diff --git a/packages/server/src/utils/directory-suggestions-memory.local.e2e.test.ts b/packages/server/src/utils/directory-suggestions-memory.local.e2e.test.ts new file mode 100644 index 0000000000..b40f65236e --- /dev/null +++ b/packages/server/src/utils/directory-suggestions-memory.local.e2e.test.ts @@ -0,0 +1,55 @@ +import { spawn } from "node:child_process"; +import { fileURLToPath } from "node:url"; +import { expect, it } from "vitest"; + +// Opt-in reproduction: prepare the directory tree with scripts/repro-directory-search-memory.py, +// then pass its location as PASEO_REPRO_SEARCH_ROOT. This exercises real filesystem calls. +it.skipIf(!process.env.PASEO_REPRO_SEARCH_ROOT)( + "finishes overlapping directory searches without exhausting the Node heap", + async () => { + const modulePath = fileURLToPath(new URL("./directory-suggestions.ts", import.meta.url)); + const source = ` + const { searchDirectoryEntries } = await import(${JSON.stringify(modulePath)}); + const query = "zzzzzzzzzzzzzz"; + await Promise.all(Array.from({ length: query.length }, (_, index) => + searchDirectoryEntries({ + root: process.env.PASEO_REPRO_SEARCH_ROOT, + query: query.slice(0, index + 1), + pathFormat: "absolute", + includeFiles: false, + includeDirectories: true, + pathQueryPolicy: "rooted", + rootAliases: ["~"], + blankQueryBehavior: "none", + confidentResultScanThreshold: 5000, + limit: 30, + }) + )); + `; + const result = await new Promise<{ + code: number | null; + signal: string | null; + stderr: string; + }>((resolve, reject) => { + const child = spawn( + process.execPath, + ["--import", "tsx", "--input-type=module", "--eval", source], + { + stdio: ["ignore", "ignore", "pipe"], + env: process.env, + }, + ); + let stderr = ""; + child.stderr.setEncoding("utf8"); + child.stderr.on("data", (data: string) => { + stderr += data; + }); + child.on("error", reject); + child.on("close", (code, signal) => resolve({ code, signal, stderr })); + }); + expect(result.stderr).not.toContain("JavaScript heap out of memory"); + expect(result.signal).toBeNull(); + expect(result.code).toBe(0); + }, + 120_000, +); diff --git a/scripts/repro-directory-search-memory.py b/scripts/repro-directory-search-memory.py new file mode 100644 index 0000000000..20da93c83c --- /dev/null +++ b/scripts/repro-directory-search-memory.py @@ -0,0 +1,25 @@ +"""Create the real directory tree for the opt-in directory-search memory regression test. + +Run from the checkout root, then run: +PASEO_REPRO_SEARCH_ROOT="$PWD/.diagnose-4879/home" npm exec --workspace=@getpaseo/server -- vitest run src/utils/directory-suggestions-memory.local.e2e.test.ts +The fixture needs approximately 6 GB of disk space. No heap limit or delay is injected. +""" +import os +import time + +root = os.path.join(os.getcwd(), ".diagnose-4879", "home") +os.makedirs(root, exist_ok=True) +start = time.monotonic() +for branch_index in range(100): + branch = os.path.join(root, f"repos-{branch_index:03}") + os.makedirs(branch, exist_ok=True) + for package_index in range(140): + package = os.path.join(branch, f"packages-{package_index:03}") + os.makedirs(package, exist_ok=True) + for component_index in range(100): + os.makedirs(os.path.join(package, f"component-{component_index:03}"), exist_ok=True) + if branch_index % 10 == 9: + print(f"{branch_index + 1} branches built in {time.monotonic() - start:.1f}s", flush=True) +for name in ["ai-credential-refresh", "ai-engineer-reference", "ai-tevv", "gmail-local-sync"]: + os.makedirs(os.path.join(root, "workspace", "projects", name), exist_ok=True) +print("Created 1,414,106 directories", flush=True)