From e90f9d06ba05a2d334036998ba89cfbd136c98a2 Mon Sep 17 00:00:00 2001 From: "anqiang.ex" Date: Tue, 22 Sep 2026 14:16:28 +0800 Subject: [PATCH] fix(session): ignore impossible usage reports in compaction preflight --- packages/opencode/src/session/overflow.ts | 11 ++++- .../opencode/test/session/compaction.test.ts | 40 +++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/session/overflow.ts b/packages/opencode/src/session/overflow.ts index 3374d1c9f8d4..fb6baf34b874 100644 --- a/packages/opencode/src/session/overflow.ts +++ b/packages/opencode/src/session/overflow.ts @@ -28,7 +28,14 @@ export function isOverflow(input: { if (input.cfg.compaction?.auto === false) return false if (input.model.limit.context === 0) return false - const count = - input.tokens.total || input.tokens.input + input.tokens.output + input.tokens.cache.read + input.tokens.cache.write + const tokens = input.tokens + const prompt = tokens.input + tokens.cache.read + tokens.cache.write + // A real prompt can never exceed the model's context window. Some providers report + // impossible usage after image-heavy histories (#50474); trusting it makes + // auto-compaction loop on every step, so ignore such reports here. Genuine + // overflow still recovers through the ContextOverflowError path. + if (prompt > input.model.limit.context) return false + + const count = tokens.total || prompt + tokens.output return count >= usable(input) } diff --git a/packages/opencode/test/session/compaction.test.ts b/packages/opencode/test/session/compaction.test.ts index c76dd98b8614..943e53ea0a17 100644 --- a/packages/opencode/test/session/compaction.test.ts +++ b/packages/opencode/test/session/compaction.test.ts @@ -561,6 +561,46 @@ describe("session.compaction.isOverflow", () => { }, ), ) + + // ─── Regression: #50474 ─────────────────────────────────────────────── + // A provider reported usage.input 24x the model's context window. isOverflow + // trusted it, so auto-compaction re-triggered every step until interrupted. + + it.live( + "returns false when reported input exceeds the model's context window", + provideTmpdirInstance(() => + Effect.gen(function* () { + const compact = yield* SessionCompaction.Service + const model = createModel({ context: 1_048_576, input: 1_048_576, output: 384_000 }) + const tokens = { input: 25_391_744, output: 2_041_207, reasoning: 0, cache: { read: 0, write: 0 } } + expect(yield* compact.isOverflow({ tokens, model })).toBe(false) + }), + ), + ) + + it.live( + "returns false when input plus cache exceeds the model's context window", + provideTmpdirInstance(() => + Effect.gen(function* () { + const compact = yield* SessionCompaction.Service + const model = createModel({ context: 1_048_576, output: 384_000 }) + const tokens = { input: 600_000, output: 10_000, reasoning: 0, cache: { read: 600_000, write: 0 } } + expect(yield* compact.isOverflow({ tokens, model })).toBe(false) + }), + ), + ) + + it.live( + "still returns true when the prompt exactly fills the context window", + provideTmpdirInstance(() => + Effect.gen(function* () { + const compact = yield* SessionCompaction.Service + const model = createModel({ context: 100_000, output: 32_000 }) + const tokens = { input: 100_000, output: 0, reasoning: 0, cache: { read: 0, write: 0 } } + expect(yield* compact.isOverflow({ tokens, model })).toBe(true) + }), + ), + ) }) describe("session.compaction.create", () => {