From 8fb1460546245fc731a4e76eb79ba59c2d698a8e Mon Sep 17 00:00:00 2001 From: Sawyer Date: Thu, 3 Sep 2026 00:46:19 -0700 Subject: [PATCH 1/4] Add tests for child-runtime cancel rejections during abort --- ...strate-factory-child-cancel-report.test.ts | 151 ++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 apps/sidecar/test/workflow-substrate-factory-child-cancel-report.test.ts diff --git a/apps/sidecar/test/workflow-substrate-factory-child-cancel-report.test.ts b/apps/sidecar/test/workflow-substrate-factory-child-cancel-report.test.ts new file mode 100644 index 000000000..804a28702 --- /dev/null +++ b/apps/sidecar/test/workflow-substrate-factory-child-cancel-report.test.ts @@ -0,0 +1,151 @@ +// Force handle.cancel to reject during parent abort and prove the +// fire-and-forget call reports through reportError instead of becoming +// an unhandled rejection. + +import { afterAll, beforeAll, expect, mock, test } from "bun:test"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +import { defineAgent } from "@intx/agent"; +import { generateKeyPair } from "@intx/crypto"; +import { + createRepoStore, + workflowRunKindHandler, + WORKFLOW_RUN_GITIGNORE_PATH, +} from "@intx/hub-sessions"; +import type { AuthorizeFn } from "@intx/hub-sessions"; +import type { + RepoId, + WorkflowRunWorkflowProcessPrincipal, +} from "@intx/hub-sessions/substrate"; +import type { KeyPair } from "@intx/types/runtime"; +import * as workflow from "@intx/workflow"; +import { + createInMemoryRepoStore, + createInMemoryScheduler, + defineWorkflow, + step, +} from "@intx/workflow"; +import type { RunChildWorkflow } from "@intx/workflow-host"; + +const REF = "refs/heads/main"; +const DEPLOYMENT_ID = "deployment-cancel-report"; +const WORKFLOW_RUN_REPO_ID: RepoId = { + kind: "workflow-run", + id: DEPLOYMENT_ID, +}; +const allowAll: AuthorizeFn = () => ({ allowed: true }); +const PRINCIPAL: WorkflowRunWorkflowProcessPrincipal = { + kind: "workflow-process", + anchorRunId: DEPLOYMENT_ID, +}; + +const tempDirs: string[] = []; +let signingKey: KeyPair; + +beforeAll(async () => { + signingKey = await generateKeyPair(); +}); + +afterAll(async () => { + for (const d of tempDirs.splice(0)) { + await fs.promises.rm(d, { recursive: true, force: true }).catch(() => { + /* best effort */ + }); + } +}); + +test("a cancel rejection during abort is reported and is not unhandled", async () => { + const forced = new Error("forced cancel reject"); + const cancelMock = mock(() => Promise.reject(forced)); + const reportErrorMock = mock( + (_error: unknown, _context: unknown) => "ref-test", + ); + + mock.module("@intx/workflow", () => ({ + ...workflow, + runtimeRun: () => ({ + runId: "run-child-forced-cancel", + complete: new Promise(() => undefined), + cancel: cancelMock, + signal: async () => undefined, + }), + })); + mock.module("@corbits/error-sink", () => ({ + reportError: reportErrorMock, + })); + + const { createSidecarRunChild } = + await import("../src/workflow-substrate-factory/child-runtime"); + + const dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), "run-child-cancel-report-"), + ); + tempDirs.push(dataDir); + const substrate = createRepoStore({ + dataDir, + signingKey, + handlers: { "workflow-run": workflowRunKindHandler }, + authorize: allowAll, + }); + await substrate.writeTree({ kind: "hub" }, WORKFLOW_RUN_REPO_ID, REF, { + files: { [WORKFLOW_RUN_GITIGNORE_PATH]: "" }, + message: "genesis", + }); + + const runChild: RunChildWorkflow = createSidecarRunChild({ + substrate, + workflowRunRepoId: WORKFLOW_RUN_REPO_ID, + workflowRunRef: REF, + principal: PRINCIPAL, + scheduler: createInMemoryScheduler({ + repoStore: createInMemoryRepoStore(), + clock: () => new Date(), + }), + invokeStep: async () => ({ output: { done: true } }), + }); + + const agent = defineAgent({ + id: "child-step", + systemPrompt: "s", + tools: [], + capabilities: [], + inference: { sources: [{ provider: "anthropic", model: "m" }] }, + }); + const definition = defineWorkflow({ + id: "child-wf-cancel-report", + trigger: { type: "manual" }, + steps: { s: step({ agent }) }, + }); + + const unhandled: unknown[] = []; + const onUnhandled = (reason: unknown) => { + unhandled.push(reason); + }; + process.on("unhandledRejection", onUnhandled); + try { + const abort = new AbortController(); + abort.abort(); + void runChild({ + definition, + definitionRef: REF, + childRunId: "run-child-cancel-report", + input: { text: "event" }, + parentRunId: "run-parent", + parentStepId: "section", + signal: abort.signal, + }); + await Bun.sleep(50); + expect(cancelMock).toHaveBeenCalled(); + expect(reportErrorMock).toHaveBeenCalledTimes(1); + expect(reportErrorMock.mock.calls[0]?.[0]).toBe(forced); + expect(reportErrorMock.mock.calls[0]?.[1]).toMatchObject({ + operation: "sidecar.child-runtime.cancel", + }); + expect(unhandled).toEqual([]); + } finally { + process.off("unhandledRejection", onUnhandled); + mock.restore(); + } +}); From 9fc317f69f3217cf8a58e932a12ce381d270e6fd Mon Sep 17 00:00:00 2001 From: Sawyer Date: Thu, 3 Sep 2026 00:46:23 -0700 Subject: [PATCH 2/4] Report child-runtime cancel and stop rejections Fire-and-forget handle.cancel and signalChannel.stop calls now catch and report through reportError so a rejected cancel during parent abort cannot become an unhandled rejection. --- .../child-runtime.ts | 50 ++++++++++++++++--- 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/apps/sidecar/src/workflow-substrate-factory/child-runtime.ts b/apps/sidecar/src/workflow-substrate-factory/child-runtime.ts index 556ddeaea..30f93540e 100644 --- a/apps/sidecar/src/workflow-substrate-factory/child-runtime.ts +++ b/apps/sidecar/src/workflow-substrate-factory/child-runtime.ts @@ -43,12 +43,34 @@ import { type SourcesSnapshotRef, } from "@intx/workflow-host"; import type { MailPartReader } from "@intx/types/runtime"; +import { reportError } from "@corbits/error-sink"; import { parseStepInferenceSources, type StepInferenceSourceTable, } from "./config"; +function reportChildRuntimeRejection( + error: unknown, + operation: string, + childRunId: string, +): void { + reportError(error, { + operation, + extra: { childRunId }, + }); +} + +function fireAndForget( + work: Promise, + operation: string, + childRunId: string, +): void { + void work.catch((error: unknown) => { + reportChildRuntimeRejection(error, operation, childRunId); + }); +} + /** * Real per-step invoker for an onTrigger BODY child, distinct from the * `StepInvoker` stub the shared `SidecarRunChildDeps.invokeStep` carries for @@ -245,7 +267,11 @@ export function createSidecarRunChild( // workflow-process-signed cancel would be refused and a parent abort // would surface as a failed rather than cancelled child. const cancelOnAbort = (): void => { - void handle.cancel("supervisor-operator", "parent cancelled"); + fireAndForget( + handle.cancel("supervisor-operator", "parent cancelled"), + "sidecar.child-runtime.cancel", + childRunId, + ); }; if (signal.aborted) { cancelOnAbort(); @@ -470,7 +496,11 @@ export function createSidecarSpawnSuspendableChild( // cancel would be refused and a parent abort would surface as a failed // rather than cancelled child. const cancelOnAbort = (): void => { - void handle.cancel("supervisor-operator", "parent cancelled"); + fireAndForget( + handle.cancel("supervisor-operator", "parent cancelled"), + "sidecar.child-runtime.cancel", + childRunId, + ); }; if (signal.aborted) { cancelOnAbort(); @@ -491,7 +521,11 @@ export function createSidecarSpawnSuspendableChild( }) .finally(() => { signal.removeEventListener("abort", cancelOnAbort); - void signalChannel.stop(); + fireAndForget( + signalChannel.stop(), + "sidecar.child-runtime.signal-channel-stop", + childRunId, + ); notify(); }); @@ -505,9 +539,13 @@ export function createSidecarSpawnSuspendableChild( // Cancel the child so its terminal (and the channel teardown // tied to it) fires, then surface the error: the throw lands // the section run's terminal via `runOnTrigger`. - void handle.cancel( - "supervisor-operator", - "onTrigger body re-armed an unsupported input park", + fireAndForget( + handle.cancel( + "supervisor-operator", + "onTrigger body re-armed an unsupported input park", + ), + "sidecar.child-runtime.cancel", + childRunId, ); throw event.error; } From dee9a5ac308395af55c3b4ec7224f39de0c0afbf Mon Sep 17 00:00:00 2001 From: Sawyer Date: Thu, 3 Sep 2026 11:33:40 -0700 Subject: [PATCH 3/4] Isolate child-runtime cancel-report tests from mock.module bun's mock.module replaces @intx/workflow process-wide and cannot be undone for later files in the same bun test process. The cancel-report test's never-resolving runtimeRun then hung the suspendable-child suite. Inject runtimeRun and reportError through SidecarRunChildDeps instead. --- .../child-runtime.ts | 43 +++++++++++++------ ...strate-factory-child-cancel-report.test.ts | 31 ++++++------- 2 files changed, 41 insertions(+), 33 deletions(-) diff --git a/apps/sidecar/src/workflow-substrate-factory/child-runtime.ts b/apps/sidecar/src/workflow-substrate-factory/child-runtime.ts index 30f93540e..650d76cbe 100644 --- a/apps/sidecar/src/workflow-substrate-factory/child-runtime.ts +++ b/apps/sidecar/src/workflow-substrate-factory/child-runtime.ts @@ -50,24 +50,17 @@ import { type StepInferenceSourceTable, } from "./config"; -function reportChildRuntimeRejection( - error: unknown, - operation: string, - childRunId: string, -): void { - reportError(error, { - operation, - extra: { childRunId }, - }); -} - function fireAndForget( work: Promise, operation: string, childRunId: string, + report: typeof reportError, ): void { void work.catch((error: unknown) => { - reportChildRuntimeRejection(error, operation, childRunId); + report(error, { + operation, + extra: { childRunId }, + }); }); } @@ -176,6 +169,20 @@ export interface SidecarRunChildDeps { * a monotonic counter combined with a random suffix. */ newId?: (prefix: string) => string; + /** + * Test seam only — no production caller ever sets this. Defaults to + * `@intx/workflow`'s `runtimeRun`. Exists because bun's `mock.module` + * replaces a module process-wide and cannot be undone for later files + * in the same `bun test` process. + */ + runtimeRun?: typeof runtimeRun; + /** + * Test seam only — no production caller ever sets this. Defaults to + * `@corbits/error-sink`'s `reportError`. Paired with `runtimeRun` so a + * cancel-rejection test can observe reports without a process-wide + * module swap. + */ + reportError?: typeof reportError; } /** @@ -206,6 +213,8 @@ export function createSidecarRunChild( const directors = deps.directors ?? createDefaultDirectorRegistry(); const clock = deps.clock ?? defaultClock; const newId = deps.newId ?? defaultNewId; + const startRun = deps.runtimeRun ?? runtimeRun; + const report = deps.reportError ?? reportError; // The in-process child runs under real supervisor authority; its // control-plane cancel (`CancelRequested`) must be signed by a supervisor // principal, which the kind handler requires and the substrate authorizes @@ -256,7 +265,7 @@ export function createSidecarRunChild( childRunId, }); try { - const handle = runtimeRun(rewrittenDefinition, env, { + const handle = startRun(rewrittenDefinition, env, { runId: childRunId, triggerPayload: input, }); @@ -271,6 +280,7 @@ export function createSidecarRunChild( handle.cancel("supervisor-operator", "parent cancelled"), "sidecar.child-runtime.cancel", childRunId, + report, ); }; if (signal.aborted) { @@ -329,6 +339,8 @@ export function createSidecarSpawnSuspendableChild( const directors = deps.directors ?? createDefaultDirectorRegistry(); const clock = deps.clock ?? defaultClock; const newId = deps.newId ?? defaultNewId; + const startRun = deps.runtimeRun ?? runtimeRun; + const report = deps.reportError ?? reportError; // The in-process body child runs under real supervisor authority; its // control-plane cancel (`CancelRequested`) must be signed by a supervisor // principal, which the kind handler requires and the substrate authorizes @@ -481,7 +493,7 @@ export function createSidecarSpawnSuspendableChild( // silently (a re-park does not re-fire onPark), and the caller relays // the grant via resume on the correlation it recovered from its own // log. On a fresh spawn, seed the run with the event's trigger payload. - const handle = runtimeRun( + const handle = startRun( rewrittenDefinition, env, resumeFromEvents !== undefined @@ -500,6 +512,7 @@ export function createSidecarSpawnSuspendableChild( handle.cancel("supervisor-operator", "parent cancelled"), "sidecar.child-runtime.cancel", childRunId, + report, ); }; if (signal.aborted) { @@ -525,6 +538,7 @@ export function createSidecarSpawnSuspendableChild( signalChannel.stop(), "sidecar.child-runtime.signal-channel-stop", childRunId, + report, ); notify(); }); @@ -546,6 +560,7 @@ export function createSidecarSpawnSuspendableChild( ), "sidecar.child-runtime.cancel", childRunId, + report, ); throw event.error; } diff --git a/apps/sidecar/test/workflow-substrate-factory-child-cancel-report.test.ts b/apps/sidecar/test/workflow-substrate-factory-child-cancel-report.test.ts index 804a28702..8392ea400 100644 --- a/apps/sidecar/test/workflow-substrate-factory-child-cancel-report.test.ts +++ b/apps/sidecar/test/workflow-substrate-factory-child-cancel-report.test.ts @@ -1,6 +1,8 @@ // Force handle.cancel to reject during parent abort and prove the // fire-and-forget call reports through reportError instead of becoming -// an unhandled rejection. +// an unhandled rejection. Injected via SidecarRunChildDeps test seams +// rather than bun's `mock.module`, whose process-wide registry swap +// cannot be undone for later files in the same `bun test` process. import { afterAll, beforeAll, expect, mock, test } from "bun:test"; import fs from "node:fs"; @@ -20,7 +22,6 @@ import type { WorkflowRunWorkflowProcessPrincipal, } from "@intx/hub-sessions/substrate"; import type { KeyPair } from "@intx/types/runtime"; -import * as workflow from "@intx/workflow"; import { createInMemoryRepoStore, createInMemoryScheduler, @@ -29,6 +30,8 @@ import { } from "@intx/workflow"; import type { RunChildWorkflow } from "@intx/workflow-host"; +import { createSidecarRunChild } from "../src/workflow-substrate-factory/child-runtime"; + const REF = "refs/heads/main"; const DEPLOYMENT_ID = "deployment-cancel-report"; const WORKFLOW_RUN_REPO_ID: RepoId = { @@ -63,22 +66,6 @@ test("a cancel rejection during abort is reported and is not unhandled", async ( (_error: unknown, _context: unknown) => "ref-test", ); - mock.module("@intx/workflow", () => ({ - ...workflow, - runtimeRun: () => ({ - runId: "run-child-forced-cancel", - complete: new Promise(() => undefined), - cancel: cancelMock, - signal: async () => undefined, - }), - })); - mock.module("@corbits/error-sink", () => ({ - reportError: reportErrorMock, - })); - - const { createSidecarRunChild } = - await import("../src/workflow-substrate-factory/child-runtime"); - const dataDir = await fs.promises.mkdtemp( path.join(os.tmpdir(), "run-child-cancel-report-"), ); @@ -104,6 +91,13 @@ test("a cancel rejection during abort is reported and is not unhandled", async ( clock: () => new Date(), }), invokeStep: async () => ({ output: { done: true } }), + runtimeRun: () => ({ + runId: "run-child-forced-cancel", + complete: new Promise(() => undefined), + cancel: cancelMock, + signal: async () => undefined, + }), + reportError: reportErrorMock, }); const agent = defineAgent({ @@ -146,6 +140,5 @@ test("a cancel rejection during abort is reported and is not unhandled", async ( expect(unhandled).toEqual([]); } finally { process.off("unhandledRejection", onUnhandled); - mock.restore(); } }); From 66305f54f5dd20ead8a91ed016577dbfeff700ef Mon Sep 17 00:00:00 2001 From: Sawyer Date: Thu, 3 Sep 2026 14:57:35 -0700 Subject: [PATCH 4/4] Add tests for child-runtime stop and suspendable-cancel rejections signalChannel.stop and spawn-suspendable abort cancel already report through fire-and-forget. Pin that those rejections go through reportError and never become unhandled rejections. --- .../child-runtime.ts | 11 +- ...strate-factory-child-cancel-report.test.ts | 191 +++++++++++++++++- 2 files changed, 199 insertions(+), 3 deletions(-) diff --git a/apps/sidecar/src/workflow-substrate-factory/child-runtime.ts b/apps/sidecar/src/workflow-substrate-factory/child-runtime.ts index 650d76cbe..02d1d1f11 100644 --- a/apps/sidecar/src/workflow-substrate-factory/child-runtime.ts +++ b/apps/sidecar/src/workflow-substrate-factory/child-runtime.ts @@ -183,6 +183,13 @@ export interface SidecarRunChildDeps { * module swap. */ reportError?: typeof reportError; + /** + * Test seam only — no production caller ever sets this. Defaults to + * `@intx/workflow-host`'s `createWorkflowHostSignalChannel`. Paired with + * `reportError` so a stop-rejection test can observe reports without a + * process-wide module swap. + */ + createSignalChannel?: typeof createWorkflowHostSignalChannel; } /** @@ -635,7 +642,9 @@ function buildChildRunEnv(args: { runId: childRunId, ref: deps.workflowRunRef, }); - const signalChannel = createWorkflowHostSignalChannel({ + const startChannel = + deps.createSignalChannel ?? createWorkflowHostSignalChannel; + const signalChannel = startChannel({ repoStore: deps.substrate, principal: deps.principal, repoId: deps.workflowRunRepoId, diff --git a/apps/sidecar/test/workflow-substrate-factory-child-cancel-report.test.ts b/apps/sidecar/test/workflow-substrate-factory-child-cancel-report.test.ts index 8392ea400..b367ff7b6 100644 --- a/apps/sidecar/test/workflow-substrate-factory-child-cancel-report.test.ts +++ b/apps/sidecar/test/workflow-substrate-factory-child-cancel-report.test.ts @@ -28,9 +28,15 @@ import { defineWorkflow, step, } from "@intx/workflow"; -import type { RunChildWorkflow } from "@intx/workflow-host"; +import { + createWorkflowHostSignalChannel, + type RunChildWorkflow, +} from "@intx/workflow-host"; -import { createSidecarRunChild } from "../src/workflow-substrate-factory/child-runtime"; +import { + createSidecarRunChild, + createSidecarSpawnSuspendableChild, +} from "../src/workflow-substrate-factory/child-runtime"; const REF = "refs/heads/main"; const DEPLOYMENT_ID = "deployment-cancel-report"; @@ -142,3 +148,184 @@ test("a cancel rejection during abort is reported and is not unhandled", async ( process.off("unhandledRejection", onUnhandled); } }); + +test("a signalChannel.stop rejection is reported and is not unhandled", async () => { + const forced = new Error("forced stop reject"); + const stopMock = mock(() => Promise.reject(forced)); + const reportErrorMock = mock( + (_error: unknown, _context: unknown) => "ref-test", + ); + + const dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), "run-child-stop-report-"), + ); + tempDirs.push(dataDir); + const substrate = createRepoStore({ + dataDir, + signingKey, + handlers: { "workflow-run": workflowRunKindHandler }, + authorize: allowAll, + }); + await substrate.writeTree({ kind: "hub" }, WORKFLOW_RUN_REPO_ID, REF, { + files: { [WORKFLOW_RUN_GITIGNORE_PATH]: "" }, + message: "genesis", + }); + + const spawn = createSidecarSpawnSuspendableChild({ + substrate, + workflowRunRepoId: WORKFLOW_RUN_REPO_ID, + workflowRunRef: REF, + principal: PRINCIPAL, + scheduler: createInMemoryScheduler({ + repoStore: createInMemoryRepoStore(), + clock: () => new Date(), + }), + invokeStep: async () => ({ output: { done: true } }), + runtimeRun: () => ({ + runId: "run-child-forced-stop", + complete: Promise.resolve({ + runId: "run-child-forced-stop", + terminalStatus: "completed" as const, + outputs: {}, + events: [], + }), + cancel: async () => undefined, + signal: async () => undefined, + }), + reportError: reportErrorMock, + createSignalChannel: (opts) => { + const channel = createWorkflowHostSignalChannel(opts); + return { ...channel, stop: stopMock }; + }, + }); + + const agent = defineAgent({ + id: "child-step", + systemPrompt: "s", + tools: [], + capabilities: [], + inference: { sources: [{ provider: "anthropic", model: "m" }] }, + }); + const definition = defineWorkflow({ + id: "child-wf-stop-report", + trigger: { type: "manual" }, + steps: { s: step({ agent }) }, + }); + + const unhandled: unknown[] = []; + const onUnhandled = (reason: unknown) => { + unhandled.push(reason); + }; + process.on("unhandledRejection", onUnhandled); + try { + await spawn( + { + definition, + definitionRef: REF, + childRunId: "run-child-stop-report", + input: { text: "event" }, + parentRunId: "run-parent", + parentStepId: "section", + signal: new AbortController().signal, + }, + () => undefined, + ); + await Bun.sleep(50); + expect(stopMock).toHaveBeenCalled(); + expect(reportErrorMock).toHaveBeenCalledTimes(1); + expect(reportErrorMock.mock.calls[0]?.[0]).toBe(forced); + expect(reportErrorMock.mock.calls[0]?.[1]).toMatchObject({ + operation: "sidecar.child-runtime.signal-channel-stop", + }); + expect(unhandled).toEqual([]); + } finally { + process.off("unhandledRejection", onUnhandled); + } +}); + +test("a spawn-suspendable abort cancel rejection is reported and is not unhandled", async () => { + const forced = new Error("forced suspendable cancel reject"); + const cancelMock = mock(() => Promise.reject(forced)); + const reportErrorMock = mock( + (_error: unknown, _context: unknown) => "ref-test", + ); + + const dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), "run-suspendable-cancel-report-"), + ); + tempDirs.push(dataDir); + const substrate = createRepoStore({ + dataDir, + signingKey, + handlers: { "workflow-run": workflowRunKindHandler }, + authorize: allowAll, + }); + await substrate.writeTree({ kind: "hub" }, WORKFLOW_RUN_REPO_ID, REF, { + files: { [WORKFLOW_RUN_GITIGNORE_PATH]: "" }, + message: "genesis", + }); + + const spawn = createSidecarSpawnSuspendableChild({ + substrate, + workflowRunRepoId: WORKFLOW_RUN_REPO_ID, + workflowRunRef: REF, + principal: PRINCIPAL, + scheduler: createInMemoryScheduler({ + repoStore: createInMemoryRepoStore(), + clock: () => new Date(), + }), + invokeStep: async () => ({ output: { done: true } }), + runtimeRun: () => ({ + runId: "run-suspendable-forced-cancel", + complete: new Promise(() => undefined), + cancel: cancelMock, + signal: async () => undefined, + }), + reportError: reportErrorMock, + }); + + const agent = defineAgent({ + id: "child-step", + systemPrompt: "s", + tools: [], + capabilities: [], + inference: { sources: [{ provider: "anthropic", model: "m" }] }, + }); + const definition = defineWorkflow({ + id: "child-wf-suspendable-cancel-report", + trigger: { type: "manual" }, + steps: { s: step({ agent }) }, + }); + + const unhandled: unknown[] = []; + const onUnhandled = (reason: unknown) => { + unhandled.push(reason); + }; + process.on("unhandledRejection", onUnhandled); + try { + const abort = new AbortController(); + abort.abort(); + await spawn( + { + definition, + definitionRef: REF, + childRunId: "run-suspendable-cancel-report", + input: { text: "event" }, + parentRunId: "run-parent", + parentStepId: "section", + signal: abort.signal, + }, + () => undefined, + ); + await Bun.sleep(50); + expect(cancelMock).toHaveBeenCalled(); + expect(reportErrorMock).toHaveBeenCalledTimes(1); + expect(reportErrorMock.mock.calls[0]?.[0]).toBe(forced); + expect(reportErrorMock.mock.calls[0]?.[1]).toMatchObject({ + operation: "sidecar.child-runtime.cancel", + }); + expect(unhandled).toEqual([]); + } finally { + process.off("unhandledRejection", onUnhandled); + } +});