anomalyco / anomalyco/opencode

compaction.reserved silently ignored for models without limit.input (e.g. GLM-5.2) — revival of #13980

Open
#38,835 3 comments 0 reactions 1 assignee View on GitHub

@kitlangton is already working on this.

Since Jul 25, 2026.

Dominant language
TypeScript
Stars
209k
Forks
27.5k
PR merge metrics
PR metrics pending

Description

Summary

compaction.reserved is silently ignored for any model whose limit lacks an input field. This was reported in #13980, which was closed by the stale bot without being fixed — the current code still ignores reserved on this code path. Filing this to revive it with a fuller analysis and to tie together several related reports.

Environment
  • opencode: 1.17.14 (source pinned to commit 203885d24; overflow.ts identical on current HEAD)
  • Model: zhipuai/glm-5.2 (limit = { context: 1000000, output: 131072 }, no input)
Reproduction

Config:

"compaction": { "auto": true, "reserved": 648928 }

Use a built-in model that has no limit.input (e.g. zhipuai/glm-5.2). Observe that compaction does not trigger at input - reserved; it triggers at context - maxOutputTokens = 1000000 - 32000 = 968000. The reserved value has zero effect.

Root cause

packages/opencode/src/session/overflow.tsusable() has two mutually exclusive branches, and reserved only participates in one:

const reserved =
  input.cfg.compaction?.reserved ??
  Math.min(COMPACTION_BUFFER, ProviderTransform.maxOutputTokens(input.model, input.outputTokenMax))
return input.model.limit.input
  ? Math.max(0, input.model.limit.input - reserved)          // branch A: reserved applies
  : Math.max(0, context - ProviderTransform.maxOutputTokens(input.model, input.outputTokenMax))  // branch B: reserved ignored

Models without limit.input (which is common — most models.dev entries only declare context + output) fall into branch B, where reserved is computed but never used. maxOutputTokens = min(limit.output, OUTPUT_TOKEN_MAX=32000) (from provider/transform.ts), so for glm-5.2 the threshold is 1000000 - 32000 = 968000 regardless of reserved.

The config schema (packages/core/src/v1/config/provider.ts) does support limit.input on custom providers, but there is no way to override limit.input for a built-in provider's model — a custom provider with the same name as a built-in does not replace the built-in entry. So users of built-in providers cannot work around this in config.

Expected vs actual
Value
Expected threshold limit.input - reserved (reserved takes effect)
Actual threshold context - maxOutputTokens = 968000 (reserved silently ignored)
Impact

Silent failure: the user sets reserved, gets no error, but the setting never takes effect. This is a UX trap — there's no signal that the config is being ignored for their model.

Proposed fix

Make reserved apply in branch B as well:

: Math.max(0, context - ProviderTransform.maxOutputTokens(input.model, input.outputTokenMax) - reserved)

This makes reserved a consistent "trigger N tokens earlier" knob regardless of whether limit.input is declared. Default reserved is min(COMPACTION_BUFFER=20000, maxOutputTokens), so the default threshold for branch-B models shifts earlier by ~20K — safer (triggers before the hard limit) and consistent with branch A's behavior.

Open semantic question for maintainers: should reserved be subtracted from limit.input (branch A) or from context - maxOutputTokens (branch B)? These differ when limit.input != context - maxOutputTokens. The two branches should agree on the reference point.

Related issues
  • #13980 — the original report of this exact bug; closed by stale bot, unfixed. This issue revives it.
  • #32656 (open) — sibling problem in the same file, branch A: reserved capped at 20K via min(COMPACTION_BUFFER, …), below real max output. Same class of "reserved not behaving as expected" bug.
  • #30805 / PR #31891 (closed/merged) — fixed the limit.input branch only; the no-limit.input branch (this issue) was missed.
  • #36481 (open) — GLM-5.2 specifically, but about backend routing / 262K cap rather than reserved being ignored. Cross-referenced as same-model.
  • #32119 (open) — limit.input vs limit.context semantics; relevant to the open question above.
  • #24683 (open) — same end-user symptom (reserved set, compaction never triggers).
Complementary upstream

The models.dev registry also omits limit.input for these models (systematic across the zhipuai provider). I'll file that separately against models.dev. But the opencode-side fix is needed regardless, since reserved should not be silently dropped for any model.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.