Skip to content

compaction: misleading error when compaction summary exceeds the model's context window #50187

Closed as not planned

Description

@cdnhermit

Summary

Compaction fails with a misleading "Compaction summary reached the output token limit" banner followed by the raw provider error (exceed_context_size_error: request (8588 tokens) exceeds the available context size (8192 tokens), try increasing it) when the compaction summary request is larger than the provider's actual context window. No output token limit was hit — the request simply did not fit the served context. The message hides the real problem (model context too small / mismatch between configured and served limits), and compaction cannot proceed.

Environment

  • opencode version: 1.14.42
  • OS: Windows 11 Pro 10.0.26200 (x64)
  • Terminal: xterm-256color (PowerShell)
  • Shell: C:\WINDOWS\system32\cmd.exe (ComSpec)
  • Install/channel: npm global install (latest)
  • Active plugins: none found in config

Reproduction

  1. Use a provider whose served context window is smaller than the context limit opencode thinks the model has. For example: LM Studio (OpenAI-compatible) serving a model with n_ctx_slot = 8192, while opencode config declares limit.context: 65536 for the same model.

  2. Use the model until the session exceeds the server's real context (e.g. 8K tokens).

  3. Trigger compaction (auto, on overflow, or manual).

  4. Observe the result: the compaction summary finishes with an error; the UI shows:

    Compaction summary reached the output token limit
    exceed_context_size_error: request (8588 tokens) exceeds the available context size (8192 tokens), try increasing it
    

    The server log shows the same overflow on every retry (e.g. request (22254 tokens) exceeds the available context size (8192 tokens), try increasing it).

Expected Behavior

One of:

  • The compaction summary request should be sized to fit the provider's actual context window (it is currently sized against the configured limit, so a stale/overstated limit.context guarantees overflow), or
  • The server's real context should be taken into account (e.g. fetched/advertised caps), or
  • At minimum, the error surfaced to the user should be clear and actionable — e.g. "The model's context window (8192 tokens) is too small to compact this conversation. Increase the context size or start a new session." — instead of the misleading "reached the output token limit" message.

The current wording is actively harmful: it points at output-token limits and suggests increasing them, when the actual cause is that the request exceeds the available input context.

Actual Behavior

The compaction summary request is sent as-is once the configured budget allows it. When the provider rejects it with exceed_context_size_error, the user sees the "Compaction summary reached the output token limit" banner plus the raw provider error. Compaction fails and the running session cannot be compacted until the model is reloaded with a bigger context or the config is fixed.

Additional Context

  • Root cause observed locally: LM Studio server log line: srv load_model: initializing, n_slots = 4, n_ctx_slot = 8192, kv_unified = 'true', immediately followed by overflow errors on requests around 8-22K tokens.
  • Trigger scenario: the user reinstalled opencode (config reset) and LM Studio's defaultContextLength was reset to its 8192 default while the opencode config still declared limit.context: 65536. The mismatch between the configured limit and the served context is what makes this occur. This is a plausible scenario for anyone with a stale config vs. a changed local server.
  • Workaround: align limit.context in opencode config with the provider's real context size and restart both sides (confirmed working).
  • Note: in the current dev branch (packages/core/src/session/compaction.ts), a guard was added — if (Token.estimate(summaryPrompt) > context - summaryOutput) return false — so compaction skips instead of sending an oversized request. However it still sizes against the configured context, so the mismatch case (configured > served) can still slip through and produce the misleading error.

Activity

  1. pm25coder commented on Sep 21, 2026

    @pm25coder

    Confirmed and extended — I traced both gates in packages/core/src/session/compaction.ts on main and ran the arithmetic locally.

    Both gates size against the configured limit, so neither can fire here

    compactAfterOverflow takes the window from the configured limits only (compaction.ts:179) and then guards the prompt it just built:

    if (Token.estimate(summaryPrompt) > context - summaryOutput) return false   // compaction.ts:190

    compactIfNeeded reads the same number (compaction.ts:234) before handing off (compaction.ts:242). With your config — limit.context: 65536, an LM Studio slot of 8192, generation.maxTokens unset — output falls back to 0 (compaction.ts:181, 236), so:

    gate threshold vs. the served window
    auto-compaction (line 237-239, DEFAULT_BUFFER = 20000) 45,536 est tokens ≈ 182 KB 5.6×
    summary guard (line 190, SUMMARY_OUTPUT_TOKENS = 4096) 61,440 est tokens ≈ 246 KB 7.5×

    I ported the two functions verbatim and ran them under node 22, reading the constants out of the tree rather than retyping them:

    auto gate  (compaction.ts:237-239) fires above 45536 est tokens = 182144 chars = 5.56 x the served window
    summary guard (compaction.ts:190)  admits up to 61440 est tokens = 245760 chars = 7.50 x the served window
    summary request asks the provider for maxTokens = 4096 of its 8192 token window
    -> after reserving 4096 for output, input may be about 4096 tokens = 15.00 x over the guard's own headroom
    
    auto gate needs about 171484 chars of conversation ( 5.23 x the served window in chars )
    summary guard needs about 275390 chars ( 8.40 x )
    

    So with a stale limit.context the guard never gets a chance to refuse before the provider does. Two details in that arithmetic are worth naming:

    • The summary call reserves summaryOutput = min(output || 4096, 4096) for output (compaction.ts:189, 208) — half of an 8192-token window — yet the guard subtracts it from the configured 65536. It starts from roughly 15× the input budget that actually exists.
    • Token.estimate is round(length / 4) over the JS string (util/token.ts:5), applied to the serialized transcript, not to provider tokens.

    Why the raw provider error arrives with nothing around it

    In packages/core/src/session/runner/llm.ts, recoverOverflow is wired to this very function (compaction.compactAfterOverflow, llm.ts:377), and the retry is taken only when the failure classifies as an overflow (llm.ts:292). When the summary stream itself errors, compactAfterOverflow sets failed = true (compaction.ts:213) and returns a plain false (compaction.ts:221) — after which the runner republishes the original provider error verbatim (if (overflowFailure) yield* publish(overflowFailure), llm.ts:296). The return value is a bare boolean, so no caller can tell "there was no head left to summarize" apart from "the summary request overflowed as well". That is the mechanical reason a user sees exceed_context_size_error with no explanation attached.

    The information your expected message needs is already on the event

    The repo's own classifier already matches your strings (packages/llm/src/provider-error.ts:18). I ran the pattern list from that file over the messages in your report:

    TRUE  | exceed_context_size_error: request (8588 tokens) exceeds the available context size (8192 tokens), try increasing it
    TRUE  | request (22254 tokens) exceeds the available context size (8192 tokens), try increasing it
    false | Rate limit exceeded, please retry after 30 seconds.
    

    So the overflow is recognised, the event carrying it is in hand at llm.ts:296, and its message already contains both numbers. Printing your suggested sentence needs no new plumbing — the wording path just has to stop treating this case as an output-token problem.

    Suggested order of fixes

    1. Cheapest, and it removes the misleading half of the report on its own: when the failed summary stream is an overflow, surface the provider's own numbers/limits instead of the generic message.
    2. Return a reason from compactAfterOverflow instead of false (e.g. no-head / summary-too-large / summary-failed), so the runner can say "the model's window (8192 tokens) is too small to compact this conversation — raise it or start a new session".
    3. Size the summary budget against the effective input window rather than the configured one: min(configured, observed) minus summaryOutput, and clamp summaryOutput to a fraction of that window instead of min(output || 4096, 4096) against a window it never checked. The cap quoted in this very error is enough to seed a per-model cache.

    Scope note: this is source reading plus a local harness porting the two functions and the classifier — I had no LM Studio server to drive, so treat the multiples above as gate arithmetic, not as a replay of your session.

    One loose end: I could not find that banner string anywhere. It is not in the tree on main, and it is not in the build you are running either — I unpacked opencode-windows-x64@1.14.42 (the platform package behind the opencode-ai wrapper) and searched the 158,887,816-byte binary; the compaction strings it ships are the " Compaction " / " Auto Compaction " box titles and the two ContextOverflowError messages ("Conversation history too large to compact - exceeds model context limit" and "Session too large to compact - context exceeds model limit even after stripping media"). If you still see that exact wording, it is worth pinning which build produced it — the sizing defect above is independent of it.

  2. Tarquinen commented on Oct 20, 2026

    @Tarquinen
    Member

    Thanks for the report! OpenCode 2.0 is now out and development is focused on V2, so we're closing issues filed against OpenCode 1. Please upgrade (opencode upgrade --major, details: https://anoma.ly/notes/opencode-2-0), and if this is still relevant on 2.0, reopen this issue or open a new one with your 2.0 version.

    — from 𝕺𝖕𝖊𝖓𝕮𝖔𝖉𝖊

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions