Skip to content

Turns truncated at the output limit are recorded as normal completions #40146

Description

@iceteaSA

Description

When a provider stops a turn because it hit the output-token limit, the stream reports finishReason: "length". The session loop treats that as a normal stop.

Two checks in packages/opencode/src/session/prompt.ts exclude only "tool-calls" (and "unknown" in the second) — never "length":

  • :1113 — the entry gate that decides whether a session already has work to do
  • :1295 — the in-loop decision after a provider turn

Nothing anywhere in src branches on "length". The message is stored with finish: "length" and no error, so downstream sees a turn that ended cleanly.

The worst case is a turn that produced nothing usable. A subagent on an OpenAI-compatible gateway spent its entire output budget inside the reasoning channel — the stored parts were step-start, reasoning (129,961 characters), step-finish, with no text and no tool call, and finish: "length". The task completed with status ok and empty output. From the caller's side that is indistinguishable from a subagent that had nothing to say.

MessageOutputLengthError already exists for this case and has no producer:

  • packages/schema/src/v1/session.ts:36
  • packages/core/src/v1/session.ts:48
  • packages/opencode/src/session/message-error.ts:4

It is already a member of the assistant error union, and packages/opencode/src/acp/service.ts:842 already maps it to stopReason: "max_tokens". The plumbing was built for this and never connected to a source.

Steps to reproduce

  1. Use a model whose output limit is reachable in one turn (the 32K OUTPUT_TOKEN_MAX clamp in packages/opencode/src/provider/transform.ts:18 makes this common).
  2. Ask for output large enough to hit the limit — a large file write, or a long reasoning chain on a model that bills reasoning inside the output count.
  3. Observe the session end. The stored assistant message has finish: "length" and no error; a task dispatch reports ok.

Reproducible in a test by queueing a reply with finishReason: "length" against the test LLM server.

OpenCode version

dev @ 1882c33

Expected

A truncated turn should be distinguishable from a completed one. A turn that produced text or tool calls before truncating is worth continuing; a turn that produced nothing usable should surface an error rather than reporting success with empty output.

Notes

This is item 4 in the root-cause chain of #18108 (P1 in that issue's checklist), filed here on its own because #18108 covers five interacting problems and this one is separable — it needs no change to repairToolCall, the thinking-budget coordination, the doom-loop detector, or OUTPUT_TOKEN_MAX.

Related: #38747 covers the same problem class on the V2 side (streams that end before their protocol completion boundary). #26167 touches the neighbouring code in prompt.ts for empty-stream truncation retries.

I have a fix with tests and will link it here.

Metadata

Metadata

Assignees

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