diff --git a/src/agents/pi-embedded-runner/run/attempt.spawn-workspace.context-engine.test.ts b/src/agents/pi-embedded-runner/run/attempt.spawn-workspace.context-engine.test.ts index 23a92fcdc10..ce1d1ea62cd 100644 --- a/src/agents/pi-embedded-runner/run/attempt.spawn-workspace.context-engine.test.ts +++ b/src/agents/pi-embedded-runner/run/attempt.spawn-workspace.context-engine.test.ts @@ -1314,21 +1314,18 @@ describe("runEmbeddedAttempt context engine sessionKey forwarding", () => { expectInitialLockReleasedBeforePostTurnWrite(lockEvents); }); - it("preserves provider prompt errors while carrying cleanup session takeover", async () => { + it("preserves provider prompt errors when cleanup reacquire detects session takeover", async () => { const providerError = new Error("provider rejected request: HTTP 400"); - let releasingCleanupLock = false; - let cleanupTakeover: EmbeddedAttemptSessionTakeoverError | undefined; - hoisted.flushPendingToolResultsAfterIdleMock.mockImplementation(async () => { - releasingCleanupLock = true; + let acquireCount = 0; + let cleanupReacquireSessionFile: string | undefined; + hoisted.acquireSessionWriteLockMock.mockImplementation(async (params) => { + acquireCount += 1; + if (acquireCount === 3) { + cleanupReacquireSessionFile = params.sessionFile; + await fs.appendFile(params.sessionFile, '{"type":"message","id":"takeover"}\n', "utf8"); + } + return { release: async () => {} }; }); - hoisted.acquireSessionWriteLockMock.mockImplementation(async (params) => ({ - release: async () => { - if (releasingCleanupLock) { - cleanupTakeover = new EmbeddedAttemptSessionTakeoverError(params.sessionFile); - throw cleanupTakeover; - } - }, - })); const error = await createContextEngineAttemptRunner({ contextEngine: createContextEngineBootstrapAndAssemble(), @@ -1342,8 +1339,13 @@ describe("runEmbeddedAttempt context engine sessionKey forwarding", () => { expect(error).toBeInstanceOf(Error); expect((error as Error).name).toBe("EmbeddedAttemptSessionTakeoverError"); expect((error as Error).message).toBe(providerError.message); - expect((error as Error).cause).toBe(cleanupTakeover); + expect((error as Error).cause).toBeInstanceOf(EmbeddedAttemptSessionTakeoverError); + if (!cleanupReacquireSessionFile) { + throw new Error("expected cleanup lock reacquire"); + } + expect(((error as Error).cause as Error).message).toContain(cleanupReacquireSessionFile); expect((error as { promptError?: unknown }).promptError).toBe(providerError); + expect(hoisted.flushPendingToolResultsAfterIdleMock).not.toHaveBeenCalled(); }); it("keeps cleanup session takeover fatal when no provider prompt error exists", async () => { diff --git a/src/agents/pi-embedded-runner/run/attempt.ts b/src/agents/pi-embedded-runner/run/attempt.ts index 6a2f3036b33..155dd354fde 100644 --- a/src/agents/pi-embedded-runner/run/attempt.ts +++ b/src/agents/pi-embedded-runner/run/attempt.ts @@ -5196,12 +5196,17 @@ export async function runEmbeddedAttempt( } catch (err) { cleanupError = err; } + const synthesizedCleanupTakeoverError = + !cleanupError && promptError && sessionLockController.hasSessionTakeover() + ? new EmbeddedAttemptSessionTakeoverError(params.sessionFile) + : undefined; + const cleanupFailure = cleanupError ?? synthesizedCleanupTakeoverError; const shouldPreservePromptError = shouldPreservePromptErrorAfterCleanupError({ promptError, - cleanupError, + cleanupError: cleanupFailure, }); emitDiagnosticRunCompleted?.( - cleanupError + cleanupFailure ? "error" : beforeAgentRunBlocked ? "blocked" @@ -5210,26 +5215,26 @@ export async function runEmbeddedAttempt( : aborted || timedOut || idleTimedOut || timedOutDuringCompaction ? "aborted" : "completed", - shouldPreservePromptError ? promptError : (cleanupError ?? promptError), + shouldPreservePromptError ? promptError : (cleanupFailure ?? promptError), beforeAgentRunBlocked ? { blockedBy: beforeAgentRunBlockedBy ?? "before_agent_run" } : undefined, ); - if (cleanupError) { + if (cleanupFailure) { if (shouldPreservePromptError) { log.warn( `embedded attempt cleanup detected session takeover after prompt failure; preserving prompt error: ` + `runId=${params.runId} sessionId=${params.sessionId} ` + - `promptError=${formatErrorMessage(promptError)} cleanupError=${formatErrorMessage(cleanupError)}`, + `promptError=${formatErrorMessage(promptError)} cleanupError=${formatErrorMessage(cleanupFailure)}`, ); await Promise.reject( new EmbeddedAttemptPromptErrorWithCleanupTakeoverError({ promptError, - cleanupError: cleanupError as EmbeddedAttemptSessionTakeoverError, + cleanupError: cleanupFailure as EmbeddedAttemptSessionTakeoverError, }), ); } else { - await Promise.reject(cleanupError); + await Promise.reject(cleanupFailure); } } }