From 30ed3cbc994bfe6af373e5a95ef6ff42fa337dfa Mon Sep 17 00:00:00 2001 From: Dotta Date: Tue, 8 Sep 2026 13:57:26 -0500 Subject: [PATCH] fix: observe sandbox runner signal failures Co-Authored-By: Paperclip --- .../native-runtime/native-session-executor.test.ts | 10 +++++++++- .../native-runtime/native-session-executor.ts | 12 ++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/server/src/services/native-runtime/native-session-executor.test.ts b/server/src/services/native-runtime/native-session-executor.test.ts index 4f6e6fb8ff..3969258c95 100644 --- a/server/src/services/native-runtime/native-session-executor.test.ts +++ b/server/src/services/native-runtime/native-session-executor.test.ts @@ -209,7 +209,7 @@ import { } from "./native-session-executor.js"; describe("remote runner process supervision", () => { - it("detaches runnerd from the provider RPC and monitors its durable identity", async () => { + it.each([false, true])("supervises detached runnerd and observes signal failures (%s)", async (signalFails) => { let launchNonce = ""; const execute = vi.fn( async (input: { @@ -272,6 +272,7 @@ describe("remote runner process supervision", () => { }; } if (label === "paperclip-runner-signal") { + if (signalFails) throw new Error("Sandbox state change in progress"); return { exitCode: 0, signal: null, @@ -284,6 +285,7 @@ describe("remote runner process supervision", () => { }, ); const onSpawn = vi.fn(async () => undefined); + const onLog = vi.fn(async () => undefined); const launcher = createRemoteRunnerProcessLauncher({ target: { kind: "remote", @@ -299,6 +301,7 @@ describe("remote runner process supervision", () => { diagnosticsDirectory: "/runtime/diagnostics", runnerInstanceId: "runner-remote", onSpawn, + onLog, }); const handle = launcher({ @@ -342,6 +345,11 @@ describe("remote runner process supervision", () => { ), ).toBe(true), ); + if (signalFails) { + await vi.waitFor(() => expect(onLog).toHaveBeenCalledWith( + "stderr", expect.stringContaining("Failed to signal sandbox runner: Sandbox state change in progress"), + )); + } }); it("terminates a detached runner when its process identity cannot be adopted", async () => { diff --git a/server/src/services/native-runtime/native-session-executor.ts b/server/src/services/native-runtime/native-session-executor.ts index 72c5248b6a..06e49ab7d5 100644 --- a/server/src/services/native-runtime/native-session-executor.ts +++ b/server/src/services/native-runtime/native-session-executor.ts @@ -5898,6 +5898,18 @@ export function createRemoteRunnerProcessLauncher(input: { ], bypassSession: true, timeoutMs: 10_000, + }).catch(async (error: unknown) => { + // kill() is synchronous, so it cannot return the provider promise to + // its caller. A stopping/replaced sandbox may reject the signal RPC; + // observe that rejection without taking down the host process. + const message = `[paperclip] Failed to signal sandbox runner: ${error instanceof Error ? error.message : String(error)}\n`; + try { + if (input.onLog) await input.onLog("stderr", message); + else console.warn(message.trimEnd()); + } catch { + // The run log may already be closed during shutdown. + console.warn(message.trimEnd()); + } }); return true; },