From a031eabe1044d81dd80cde6e4ffa8fff27fe7bbd Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Mon, 9 Nov 2026 23:05:46 +0100 Subject: [PATCH] fix(v2): end an open compaction when the query ends on its own The error idle of a query that ended went out without the turn-end cleanup of closeTurn, so a compaction reported in_progress never ended. ended() now resets it through reports.turnEnded() before the idle. Co-Authored-By: Claude --- src/tests/v2.test.ts | 35 +++++++++++++++++++++++++++++++++++ src/v2/session.ts | 2 ++ 2 files changed, 37 insertions(+) diff --git a/src/tests/v2.test.ts b/src/tests/v2.test.ts index d26dc458..5940aebf 100644 --- a/src/tests/v2.test.ts +++ b/src/tests/v2.test.ts @@ -1071,6 +1071,41 @@ describe("ACP v2 core", () => { ]); }); + it("ends an open compaction before the idle when Claude Code exits during it", async () => { + const client = v2Client(); + await client.connect(async (agent) => { + const { response, claude } = await openSession(agent); + const answer = agent.request(v2.methods.agent.session.prompt, { + sessionId: response.sessionId, + prompt: [{ type: "text", text: "/compact" }], + }); + await claude.prompt(0); + claude.send(cycleStart()); + claude.send({ + type: "system", + subtype: "status", + status: "compacting", + session_id: response.sessionId, + uuid: "compact-1", + }); + claude.end(new Error("Claude Code process exited with code 1")); + await answer.catch(() => {}); + await vi.waitFor(() => expect(client.states()).toEqual(["running", "idle"])); + }); + + expect( + client.sessionUpdates.filter( + (update) => + update.sessionUpdate === "compaction_update" || update.sessionUpdate === "state_update", + ), + ).toMatchObject([ + { sessionUpdate: "state_update", state: "running" }, + { sessionUpdate: "compaction_update", compactionId: "compact-1", status: "in_progress" }, + { sessionUpdate: "compaction_update", compactionId: "compact-1", status: "cancelled" }, + { sessionUpdate: "state_update", state: "idle", stopReason: "error" }, + ]); + }); + it("starts a session whose query ended again on resume", async () => { const client = v2Client(); await client.connect(async (agent) => { diff --git a/src/v2/session.ts b/src/v2/session.ts index 36d66021..b615f05a 100644 --- a/src/v2/session.ts +++ b/src/v2/session.ts @@ -1203,6 +1203,8 @@ export class V2Session implements RequestTarget { if (this.cancelledWork) this.output.closeUnfinished(true); else this.output.finishUnfinished("failed"); this.fileChanges?.turnClosed(this.cancelledWork); + // As `closeTurn`: a compaction still `in_progress` ends before the idle. + this.reports.turnEnded(); this.foreground = false; this.reportedState = "idle"; this.send({