From 9dd8d3c6ceea9c8600a6bca6ceb28ed556c78c7f Mon Sep 17 00:00:00 2001 From: usr-bin-roygbiv Date: Tue, 28 Jul 2026 15:27:55 +0000 Subject: [PATCH] fix: preserve native compaction failures (cherry picked from commit d13e9f30a06cad347226d2fa377cab8c086debc3) --- .../src/session/session-maintenance.ts | 2 +- ...issue-986-compaction-auth-fallback.test.ts | 33 +++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/packages/coding-agent/src/session/session-maintenance.ts b/packages/coding-agent/src/session/session-maintenance.ts index 19cd40efa..dc24d4dfe 100644 --- a/packages/coding-agent/src/session/session-maintenance.ts +++ b/packages/coding-agent/src/session/session-maintenance.ts @@ -2542,7 +2542,7 @@ export class SessionMaintenance { candidate.api, ); if (AIError.is(id, AIError.Flag.AuthFailed)) { - lastError = this.#buildCompactionAuthError(); + if (!nativeCompactionFailure) lastError = this.#buildCompactionAuthError(); break; } if (AIError.is(id, AIError.Flag.Timeout)) { diff --git a/packages/coding-agent/test/issue-986-compaction-auth-fallback.test.ts b/packages/coding-agent/test/issue-986-compaction-auth-fallback.test.ts index 1c8f1c51c..b5b0ed692 100644 --- a/packages/coding-agent/test/issue-986-compaction-auth-fallback.test.ts +++ b/packages/coding-agent/test/issue-986-compaction-auth-fallback.test.ts @@ -190,6 +190,39 @@ describe("issue #986 compaction auth fallback", () => { ]); }); + it("preserves a native transport failure when a later same-provider candidate fails authentication", async () => { + const { apiKeySpy, crossProviderModel, currentModel, sameProviderModel, triggerAutoCompaction } = + await createAutoNativeFallbackSession(); + apiKeySpy.mockImplementation(async model => + model.provider === crossProviderModel.provider ? undefined : "test-key", + ); + const attemptedModels: string[] = []; + let errorMessage: string | undefined; + session.subscribe(event => { + if (event.type === "auto_compaction_end") errorMessage = event.errorMessage; + }); + vi.spyOn(compactionModule, "compact").mockImplementation(async (_preparation, model) => { + attemptedModels.push(`${model.provider}/${model.id}`); + if (model.provider === currentModel.provider && model.id === currentModel.id) { + throw new compactionModule.NativeCompactionError(new Error("native compaction transport failed")); + } + if (model.provider === sameProviderModel.provider && model.id === sameProviderModel.id) { + throw new compactionModule.NativeCompactionError( + Object.assign(new Error("native compaction authentication failed"), { status: 401 }), + ); + } + throw new Error(`Unexpected compaction model ${model.provider}/${model.id}`); + }); + + await triggerAutoCompaction(); + + expect(attemptedModels).toEqual([ + `${currentModel.provider}/${currentModel.id}`, + `${sameProviderModel.provider}/${sameProviderModel.id}`, + ]); + expect(errorMessage).toContain("native compaction transport failed"); + }); + it("skips unauthenticated cross-provider candidates before enforcing the native boundary", async () => { const { apiKeySpy, crossProviderModel, currentModel, sameProviderModel, triggerAutoCompaction } = await createAutoNativeFallbackSession();