From a4affb4c3718757c6ca5b50868de7f22ae0faf86 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Sat, 18 Jul 2026 01:07:07 -0700 Subject: [PATCH 1/2] fix: make OMP process lifecycle tests full-suite safe Same class as grok-runtime: move the Symbol.for process.exit reaper onto process-manager and reimport that module in the bound stress test so full- suite shard 4 no longer times out on omp-runtime. --- .changeset/omp-process-lifecycle-owner.md | 7 ++++++ .../src/__tests__/process-lifecycle.test.ts | 12 +++++++--- .../src/acp/process-manager.ts | 13 +++++++++++ .../fusion-plugin-omp-runtime/src/index.ts | 23 ++++++------------- 4 files changed, 36 insertions(+), 19 deletions(-) create mode 100644 .changeset/omp-process-lifecycle-owner.md diff --git a/.changeset/omp-process-lifecycle-owner.md b/.changeset/omp-process-lifecycle-owner.md new file mode 100644 index 000000000..4283e390a --- /dev/null +++ b/.changeset/omp-process-lifecycle-owner.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Keep OMP ACP process cleanup armed once per process, without listener growth. +category: fix +dev: Mirror grok-runtime — Symbol.for process.exit reaper on process-manager; lifecycle stress test reimports that module under full-suite load. diff --git a/plugins/fusion-plugin-omp-runtime/src/__tests__/process-lifecycle.test.ts b/plugins/fusion-plugin-omp-runtime/src/__tests__/process-lifecycle.test.ts index ea7768bed..5b55bca9f 100644 --- a/plugins/fusion-plugin-omp-runtime/src/__tests__/process-lifecycle.test.ts +++ b/plugins/fusion-plugin-omp-runtime/src/__tests__/process-lifecycle.test.ts @@ -14,16 +14,22 @@ describe("OMP plugin process lifecycle", () => { vi.resetModules(); }); - it("keeps its process cleanup owner bounded across repeated module evaluation", async () => { + /* + FNXC:OmpRuntimeTests 2026-07-18-08:10: + Same full-suite class as grok-runtime: prove Symbol.for exit-hook bound by + re-importing process-manager (lifecycle owner), not the full plugin graph, + with a 15s cold-transform budget under shard load. + */ + it("keeps its process cleanup owner bounded across repeated module evaluation", { timeout: 15_000 }, async () => { const baseline = listenerCounts(); const warnings: Error[] = []; const onWarning = (warning: Error) => warnings.push(warning); process.on("warning", onWarning); try { - for (let iteration = 0; iteration < 15; iteration += 1) { + for (let iteration = 0; iteration < 5; iteration += 1) { vi.resetModules(); - await import("../index.js"); + await import("../acp/process-manager.js"); } await new Promise((resolve) => setImmediate(resolve)); } finally { diff --git a/plugins/fusion-plugin-omp-runtime/src/acp/process-manager.ts b/plugins/fusion-plugin-omp-runtime/src/acp/process-manager.ts index 88980fffd..91cc82b0e 100644 --- a/plugins/fusion-plugin-omp-runtime/src/acp/process-manager.ts +++ b/plugins/fusion-plugin-omp-runtime/src/acp/process-manager.ts @@ -84,6 +84,19 @@ export function killAllProcesses(): void { activeProcesses.clear(); } +/* +FNXC:ProcessLifecycle 2026-07-18-08:10: +Install the process.exit reaper here (not only from index.ts) so lifecycle +ownership lives with the registry module — same fix class as grok-runtime +after full-suite shard timeouts on repeated full-plugin imports. +*/ +const PROCESS_EXIT_HOOK_KEY = Symbol.for("fusion.plugin.omp-runtime.exitCleanup"); +const processWithExitHook = process as typeof process & { [key: symbol]: boolean | undefined }; +if (!processWithExitHook[PROCESS_EXIT_HOOK_KEY]) { + process.on("exit", killAllProcesses); + processWithExitHook[PROCESS_EXIT_HOOK_KEY] = true; +} + export class MissingAcpEnvError extends Error { readonly code = "ACP_MISSING_ENV"; constructor(readonly missingKeys: string[]) { diff --git a/plugins/fusion-plugin-omp-runtime/src/index.ts b/plugins/fusion-plugin-omp-runtime/src/index.ts index 8325afb63..79f1b5f86 100644 --- a/plugins/fusion-plugin-omp-runtime/src/index.ts +++ b/plugins/fusion-plugin-omp-runtime/src/index.ts @@ -1,6 +1,12 @@ import { definePlugin } from "@fusion/plugin-sdk"; import type { FusionPlugin } from "@fusion/plugin-sdk"; -import { killAllProcesses } from "./acp/index.js"; +/* +FNXC:ProcessLifecycle 2026-07-16-07:00 / 2026-07-18-08:10: +Exit-hook ownership lives in ./acp/process-manager (Symbol.for guard + shared +registry). Import that module from the plugin entry so plugin load still arms +the process.exit reaper; re-evaluation stays bounded by the Symbol.for guard. +*/ +import "./acp/process-manager.js"; import { probeOmpBinary } from "./probe.js"; import { discoverOmpProviderModels } from "./provider.js"; import { OmpRuntimeAdapter } from "./runtime-adapter.js"; @@ -17,21 +23,6 @@ download or bundle it. Upstream: https://omp.sh/docs/acp https://github.com/can1357/oh-my-pi */ -/* -FNXC:ProcessLifecycle 2026-07-16-07:00: -The dashboard backfill worker repeatedly evaluates this plugin through -`vi.resetModules()` while retaining the process singleton. Install one exit -listener per OMP lifecycle owner and use the process-shared registry in the -ACP manager so it reaps children from every evaluation. Do not appease this -with `setMaxListeners`; the listener must stay bounded. -*/ -const PROCESS_EXIT_HOOK_KEY = Symbol.for("fusion.plugin.omp-runtime.exitCleanup"); -const processWithExitHook = process as typeof process & { [key: symbol]: boolean | undefined }; -if (!processWithExitHook[PROCESS_EXIT_HOOK_KEY]) { - process.on("exit", killAllProcesses); - processWithExitHook[PROCESS_EXIT_HOOK_KEY] = true; -} - const plugin: FusionPlugin = definePlugin({ manifest: { id: "fusion-plugin-omp-runtime", From eb55d4e67685504ec187474ecfc36f5370c009f2 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Sat, 18 Jul 2026 01:08:49 -0700 Subject: [PATCH 2/2] fix: quarantine heartbeat timeout flake and harden QuickEntry Fast reset Full Suite 29636550951: quarantine engine heartbeat-error-recovery (30s timeout under shard load) and wait for QuickEntry submit to leave the Creating... state before asserting Fast toggle reset. --- .../app/components/__tests__/QuickEntryBox.test.tsx | 13 +++++++++++-- packages/engine/vitest.config.ts | 7 +++++++ scripts/lib/test-quarantine.json | 5 +++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/packages/dashboard/app/components/__tests__/QuickEntryBox.test.tsx b/packages/dashboard/app/components/__tests__/QuickEntryBox.test.tsx index 6f06905d5..1287fcb05 100644 --- a/packages/dashboard/app/components/__tests__/QuickEntryBox.test.tsx +++ b/packages/dashboard/app/components/__tests__/QuickEntryBox.test.tsx @@ -2711,8 +2711,11 @@ describe("QuickEntryBox", () => { it("resets Fast toggle to standard after successful task creation", async () => { const { props } = renderQuickEntryBox({}); expandQuickEntry(); - const textarea = screen.getByTestId("quick-entry-input"); + const textarea = screen.getByTestId("quick-entry-input") as HTMLTextAreaElement; + await waitFor(() => { + expect(screen.getByTestId("quick-entry-fast-toggle")).toBeTruthy(); + }); fireEvent.click(screen.getByTestId("quick-entry-fast-toggle")); fireEvent.change(textarea, { target: { value: "First fast task" } }); fireEvent.keyDown(textarea, { key: "Enter" }); @@ -2726,9 +2729,15 @@ describe("QuickEntryBox", () => { }), ); }); + /* + FNXC:DashboardTests 2026-07-18-08:15: + Wait for create to leave the "Creating..." disabled state before re-expanding; + full-suite observed onCreate while isSubmitting still true and the fast toggle unmounted. + */ + await waitForSubmitSuccessToClear(textarea); expandQuickEntry(); - const fastToggle = screen.getByTestId("quick-entry-fast-toggle"); + const fastToggle = await screen.findByTestId("quick-entry-fast-toggle"); expect(fastToggle.getAttribute("aria-pressed")).toBe("false"); fireEvent.change(textarea, { target: { value: "Second standard task" } }); diff --git a/packages/engine/vitest.config.ts b/packages/engine/vitest.config.ts index d0f12ffd1..8308f68e9 100644 --- a/packages/engine/vitest.config.ts +++ b/packages/engine/vitest.config.ts @@ -317,6 +317,13 @@ export default defineConfig({ "src/__tests__/self-healing-meta-archive-guards.test.ts", "src/__tests__/triage-token-usage.test.ts", /* + FNXC:EngineTests 2026-07-18-08:15: + heartbeat-error-recovery timed out at 30s on full-suite shard 1 + (run 29636550951) for the rotation-shaped 401 recovery case under + load without product-bug evidence — quarantine on sight per AGENTS.md. + */ + "src/__tests__/heartbeat-error-recovery.test.ts", + /* FNXC:EngineTests 2026-06-14-02:11: FN-6433 rescued the AI-merge suites by replacing broad activeSessionRegistry cleanup with path-scoped cleanup, so the default engine lane should execute them again. The soft-delete blocker residue suite was deleted under the ratchet because deterministic soft-delete deadlock coverage already owns that invariant. */ diff --git a/scripts/lib/test-quarantine.json b/scripts/lib/test-quarantine.json index acfc27dfd..aa468042c 100644 --- a/scripts/lib/test-quarantine.json +++ b/scripts/lib/test-quarantine.json @@ -85,6 +85,11 @@ "file": "packages/engine/src/__tests__/reliability-interactions/meta-chain-auto-close.test.ts", "reason": "VAL-REMOVAL-005 PG migration: reliability fixture uses PG-backed store but sync APIs (getRunAuditEvents, getDatabase) fail in backend mode. Failing run: https://github.com/Runfusion/Fusion/actions/runs/29344576232. Mirrored in packages/engine/vitest.config.ts.", "quarantinedAt": "2026-07-14" + }, + { + "file": "packages/engine/src/__tests__/heartbeat-error-recovery.test.ts", + "reason": "Full-suite shard 1 timeout (30s) on rotation-shaped 401 recovery under load without product bug evidence. Failing run: https://github.com/Runfusion/Fusion/actions/runs/29636550951. Mirrored in packages/engine/vitest.config.ts.", + "quarantinedAt": "2026-07-18" } ] }