Skip to content

opencode caps every model's output at 32k, and only an env var can change it #2

Description

@AlexMKX

What

opencode requests at most 32 000 output tokens from every model, regardless of
what that model can actually produce. On a reasoning model the same budget covers
reasoning and text, so a verbose thinker can be truncated before it emits
anything at all.

// opencode core
var OUTPUT_TOKEN_MAX = 32e3;
function maxOutputTokens(model, outputTokenMax = OUTPUT_TOKEN_MAX) {
  return Math.min(model.limit.output, outputTokenMax) || outputTokenMax;
}

Real limit.output per models.dev: anthropic 128 000, zai 131 072, gemini 65 536.
min(128000, 32000) = 32 000 — we ask for a quarter of what is available.

Evidence

Every finish: "length" assistant message across our session stores sums
output + reasoning to 31 995 … 32 000 — 87 of them, spanning four providers
and eight models. A single cap applied uniformly; not a model property, not a
provider quirk.

Two distinct failure shapes, both silent:

  1. Reasoning eats the budget. Two zai-coding-plan/glm-5.3* grunts:
    finish=length, reasoning 31 991 / 31 998, output 9 / 2. ~133 KB of real
    drafted work stranded in the reasoning channel, no file written. The task
    tool reports state="completed" with an empty <task_result>, so the
    orchestrator saw only "" and concluded the provider had refused — which
    nothing in the session supported.
  2. A tool call is truncated mid-JSON. Five grunts streaming a large file into
    one write call: the arguments were cut at the cap, the call never parsed,
    and the part reads tool=write status=error input={} error="Tool execution aborted". Four of the five killed the subagent session outright. That looks
    exactly like a manual cancel, which is why it went unnoticed for months.

Of the 87 truncations, 20 ended their session with the work lost.

Why it is not configurable

OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX is the only override. It is read as
flags.outputTokenMax through Effect Config from the environment; there is no
key for it in opencode.json (the config schema has no experimental section),
so a project or global config file cannot set it.

Raising provider.<id>.models.<mid>.limit.output does not help either — the
min still clamps to 32 000. The override only works downwards.

Options

  • (a) Environment variable, e.g. OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX=131072.
    The min is preserved, so one global number still gives every model its own
    real ceiling (Claude 128k, GLM 131k, Gemini 65k). No code. Downside: the flag
    is marked EXPERIMENTAL, and it is all-or-nothing across models and agents.
  • (b) A chat.params hook in this plugin. That hook receives
    maxOutputTokens as a writable output alongside agent and model, so the
    cap can be lifted per model or only for grunts, without depending on an
    experimental flag.

Cost of fixing it

Both options remove a brake. Today's 32 000 cap is a crude one — it kills good
work along with runaway work — but it is a bound. Lifted, an agent that spirals
burns up to 131 k output tokens before it stops. Worth pairing with the per-agent
steps cap the roster already supports, and with reasoning variants, which
bound the reasoning share directly.

Not this

Suppressing reasoning is not the fix and should not be considered one: on a
reasoning model it trades a truncation bug for a quality regression. Setting a
variant bounds an unbounded reasoner; it does not switch reasoning off.

Activity

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