From 66676d49e6336a75837abec247871553a7658c35 Mon Sep 17 00:00:00 2001 From: "paseo-bot[bot]" <266920839+paseo-bot[bot]@users.noreply.github.com> Date: Wed, 4 Nov 2026 01:56:00 +0000 Subject: [PATCH] fix(pi): require Reload after the native process exits (#8718) Reject new turns before accepting work into a known dead Pi runtime. Keep process liveness separate from explicit session cleanup so Reload can still release resources and resume the conversation. Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> --- .../server/agent/providers/pi/agent.test.ts | 27 +++++++++++++++++++ .../src/server/agent/providers/pi/agent.ts | 5 ++++ 2 files changed, 32 insertions(+) diff --git a/packages/server/src/server/agent/providers/pi/agent.test.ts b/packages/server/src/server/agent/providers/pi/agent.test.ts index 039d3eaf22..9ca6c821b8 100644 --- a/packages/server/src/server/agent/providers/pi/agent.test.ts +++ b/packages/server/src/server/agent/providers/pi/agent.test.ts @@ -2194,6 +2194,33 @@ describe("PiRpcAgentSession", () => { }); }); + test.each([false, true])( + "rejects new turns with Reload guidance after the Pi process exits (active: %s)", + async (active) => { + const { pi, session, events } = await createSession(); + try { + if (active) { + await session.startTurn("before crash"); + } + const runtime = pi.latestSession(); + runtime.emit({ type: "process_exit", error: "Pi exited" }); + if (active) { + await expect(events.nextTurnFailure()).resolves.toMatchObject({ error: "Pi exited" }); + } + const promptCount = runtime.prompts.length; + + for (let attempt = 0; attempt < 2; attempt += 1) { + await expect(session.startTurn("after crash")).rejects.toThrow( + "Pi process exited. Reload agent before sending another message.", + ); + } + expect(runtime.prompts).toHaveLength(promptCount); + } finally { + await session.close(); + } + }, + ); + test("fails an autonomous turn when the Pi process exits before settlement", async () => { const { pi, events } = await createSession(); const fakeSession = pi.latestSession(); diff --git a/packages/server/src/server/agent/providers/pi/agent.ts b/packages/server/src/server/agent/providers/pi/agent.ts index 198d596c9b..513f9b62e9 100644 --- a/packages/server/src/server/agent/providers/pi/agent.ts +++ b/packages/server/src/server/agent/providers/pi/agent.ts @@ -1223,6 +1223,7 @@ export class PiRpcAgentSession implements AgentSession { private readonly logger: Logger; private readonly usagePoller: PiUsagePoller; private closed = false; + private runtimeDead = false; private readonly closeController = new AbortController(); private readonly pendingExtensionHydrations = new Set>(); private pendingOutput: Promise | null = null; @@ -1305,6 +1306,9 @@ export class PiRpcAgentSession implements AgentSession { } async startTurn(prompt: AgentPromptInput, options?: AgentRunOptions): Promise { + if (this.runtimeDead) { + throw new Error("Pi process exited. Reload agent before sending another message."); + } if (this.activeTurnId) { throw new Error("A Pi turn is already active"); } @@ -2190,6 +2194,7 @@ export class PiRpcAgentSession implements AgentSession { } private handleProcessExit(error: string): void { + this.runtimeDead = true; this.rejectAllExtensionResults(new Error(error)); this.interruptingTurn = null; if (!this.activeTurnId && !this.activeTurnStarted) {