From 3cf454f7ea50bf6524fbbff12b9b6e3ef5215754 Mon Sep 17 00:00:00 2001 From: Joey Krug Date: Sat, 14 Mar 2026 12:45:58 -0400 Subject: [PATCH 1/2] fix: fall back to built-in compaction when model/apiKey unavailable When both ctx.model and runtime.model are undefined, or when the API key is missing, return undefined instead of { cancel: true } so the session falls back to truncation-based compaction rather than blocking the lane. Co-Authored-By: Claude Opus 4.6 --- .../compaction-safeguard.test.ts | 33 +++++++++++++++++-- .../pi-extensions/compaction-safeguard.ts | 8 ++--- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/src/agents/pi-extensions/compaction-safeguard.test.ts b/src/agents/pi-extensions/compaction-safeguard.test.ts index 882099f3569..68b0243360e 100644 --- a/src/agents/pi-extensions/compaction-safeguard.test.ts +++ b/src/agents/pi-extensions/compaction-safeguard.test.ts @@ -1469,7 +1469,7 @@ describe("compaction-safeguard extension model fallback", () => { apiKey: null, }); - expect(result).toEqual({ cancel: true }); + expect(result).toBeUndefined(); // KEY ASSERTION: Prove the fallback path was exercised // The handler should have called getApiKey with runtime.model (via ctx.model ?? runtime?.model) @@ -1495,11 +1495,38 @@ describe("compaction-safeguard extension model fallback", () => { apiKey: null, }); - expect(result).toEqual({ cancel: true }); + expect(result).toBeUndefined(); // Verify early return: getApiKey should NOT have been called when both models are missing expect(getApiKeyMock).not.toHaveBeenCalled(); }); + + it("falls back to built-in compaction when model is undefined (returns undefined, not cancel)", async () => { + const sessionManager = stubSessionManager(); + // Do NOT set runtime model + const mockEvent = createCompactionEvent({ messageText: "test", tokensBefore: 500 }); + const { result, getApiKeyMock } = await runCompactionScenario({ + sessionManager, + event: mockEvent, + apiKey: null, + }); + expect(result).toBeUndefined(); + expect(getApiKeyMock).not.toHaveBeenCalled(); + }); + + it("falls back to built-in compaction when API key is missing (returns undefined, not cancel)", async () => { + const sessionManager = stubSessionManager(); + const model = createAnthropicModelFixture(); + setCompactionSafeguardRuntime(sessionManager, { model }); + const mockEvent = createCompactionEvent({ messageText: "test", tokensBefore: 500 }); + const { result, getApiKeyMock } = await runCompactionScenario({ + sessionManager, + event: mockEvent, + apiKey: null, + }); + expect(result).toBeUndefined(); + expect(getApiKeyMock).toHaveBeenCalledWith(model); + }); }); describe("compaction-safeguard double-compaction guard", () => { @@ -1542,7 +1569,7 @@ describe("compaction-safeguard double-compaction guard", () => { event: mockEvent, apiKey: null, }); - expect(result).toEqual({ cancel: true }); + expect(result).toBeUndefined(); expect(getApiKeyMock).toHaveBeenCalled(); }); }); diff --git a/src/agents/pi-extensions/compaction-safeguard.ts b/src/agents/pi-extensions/compaction-safeguard.ts index 4461b97d3e0..9f3e378f060 100644 --- a/src/agents/pi-extensions/compaction-safeguard.ts +++ b/src/agents/pi-extensions/compaction-safeguard.ts @@ -740,15 +740,13 @@ export default function compactionSafeguardExtension(api: ExtensionAPI): void { "was not called and model was not passed through runtime registry.", ); } - return { cancel: true }; + return undefined; } const apiKey = await ctx.modelRegistry.getApiKey(model); if (!apiKey) { - log.warn( - "Compaction safeguard: no API key available; cancelling compaction to preserve history.", - ); - return { cancel: true }; + log.warn("Compaction safeguard: no API key available; falling back to built-in compaction."); + return undefined; } try { From b626ee75ae5c47057472edfef38707b4298d9fad Mon Sep 17 00:00:00 2001 From: Joey Krug Date: Sat, 21 Mar 2026 00:29:29 -0400 Subject: [PATCH 2/2] test: remove duplicate compaction fallback coverage --- .../pi-extensions/compaction-safeguard.test.ts | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/src/agents/pi-extensions/compaction-safeguard.test.ts b/src/agents/pi-extensions/compaction-safeguard.test.ts index 68b0243360e..cb90e1daf85 100644 --- a/src/agents/pi-extensions/compaction-safeguard.test.ts +++ b/src/agents/pi-extensions/compaction-safeguard.test.ts @@ -1480,7 +1480,7 @@ describe("compaction-safeguard extension model fallback", () => { expect(retrieved?.model).toEqual(model); }); - it("cancels compaction when both ctx.model and runtime.model are undefined", async () => { + it("falls back to built-in compaction when both ctx.model and runtime.model are undefined", async () => { const sessionManager = stubSessionManager(); // Do NOT set runtime.model (both ctx.model and runtime.model will be undefined) @@ -1501,19 +1501,6 @@ describe("compaction-safeguard extension model fallback", () => { expect(getApiKeyMock).not.toHaveBeenCalled(); }); - it("falls back to built-in compaction when model is undefined (returns undefined, not cancel)", async () => { - const sessionManager = stubSessionManager(); - // Do NOT set runtime model - const mockEvent = createCompactionEvent({ messageText: "test", tokensBefore: 500 }); - const { result, getApiKeyMock } = await runCompactionScenario({ - sessionManager, - event: mockEvent, - apiKey: null, - }); - expect(result).toBeUndefined(); - expect(getApiKeyMock).not.toHaveBeenCalled(); - }); - it("falls back to built-in compaction when API key is missing (returns undefined, not cancel)", async () => { const sessionManager = stubSessionManager(); const model = createAnthropicModelFixture();