From dd1bd2c7e6867e231be999bd89140aceabf9248b Mon Sep 17 00:00:00 2001 From: Alessandro Pogliaghi Date: Tue, 2 Jun 2026 14:22:27 +0100 Subject: [PATCH] fix(cloud-agent): tolerate interrupt errors during claude session refresh --- .../claude/claude-agent.refresh.test.ts | 23 +++++++++++++++++++ .../agent/src/adapters/claude/claude-agent.ts | 9 +++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/packages/agent/src/adapters/claude/claude-agent.refresh.test.ts b/packages/agent/src/adapters/claude/claude-agent.refresh.test.ts index e279949236..0671f6c3c0 100644 --- a/packages/agent/src/adapters/claude/claude-agent.refresh.test.ts +++ b/packages/agent/src/adapters/claude/claude-agent.refresh.test.ts @@ -278,6 +278,29 @@ describe("ClaudeAcpAgent.extMethod refresh_session", () => { ); }); + it("recovers when interrupting the old query throws Operation aborted", async () => { + const agent = makeAgent(); + const { session, oldQuery, endSpy } = installFakeSession( + agent, + "s-interrupt-throws", + ); + oldQuery.interrupt.mockRejectedValue(new Error("Operation aborted")); + + const result = await agent.extMethod(POSTHOG_METHODS.REFRESH_SESSION, { + mcpServers: freshMcpServers, + }); + + expect(result).toEqual({ refreshed: true }); + expect(endSpy).toHaveBeenCalledTimes(1); + const updated = session as unknown as { + query: SdkQueryHandle; + abortController: AbortController; + }; + expect(updated.query).toBe(createdQueries[0]); + expect(updated.query).not.toBe(oldQuery); + expect(updated.abortController.signal.aborted).toBe(false); + }); + it("re-fetches MCP tool metadata for the new query", async () => { const agent = makeAgent(); installFakeSession(agent, "s-metadata"); diff --git a/packages/agent/src/adapters/claude/claude-agent.ts b/packages/agent/src/adapters/claude/claude-agent.ts index 3a2c3a5f65..b7d7971a52 100644 --- a/packages/agent/src/adapters/claude/claude-agent.ts +++ b/packages/agent/src/adapters/claude/claude-agent.ts @@ -1089,7 +1089,14 @@ export class ClaudeAcpAgent extends BaseAcpAgent { // We allocate a fresh controller for the new Query below so aborting // the old one doesn't poison it. prev.abortController.abort(); - await prev.query.interrupt(); + try { + await prev.query.interrupt(); + } catch (error) { + this.logger.debug("Ignoring interrupt error during session refresh", { + sessionId: this.sessionId, + error: error instanceof Error ? error.message : String(error), + }); + } prev.input.end(); // Reuse every option from the running session; swap mcpServers, re-root