Skip to content

Incomplete reasoning from a truncated turn is replayed to the provider #40147

Description

@iceteaSA

Description

When history is replayed to the provider, a stored reasoning part on a same-model assistant message is replayed verbatim as a native reasoning part (packages/opencode/src/session/message-v2.ts, in the assistant branch of toModelMessagesEffect).

That is correct for Anthropic — signed thinking blocks have to round-trip intact, and there is a hand-written separator workaround immediately above for exactly that case.

It is wrong for a turn that was truncated at the output-token limit (finish: "length"). That turn's reasoning is an incomplete chain, and on a provider with no signature to preserve there is nothing gained by sending it back.

Observed on an OpenAI-compatible gateway. A subagent turn spent its entire output budget inside the reasoning channel — stored parts were step-start, reasoning (129,961 characters), step-finish, with no text and no tool call. Resuming that session replayed the incomplete chain; the model continued it and truncated again at the same limit, producing 132,948 characters over 4m19s with no text and no tool calls.

The isolating evidence: a fresh dispatch of the same task on the same model completed normally in 274s. The difference was the replayed history, not the task or the model.

Some gateways bill reasoning inside the ordinary output count, so tokens.reasoning reads 0 on exactly these turns — the shape is easy to miss when looking at token counts rather than parts.

Steps to reproduce

  1. Run a session on a model whose reasoning is not signature-carrying (any OpenAI-compatible gateway lane).
  2. Get a turn to truncate at the output limit while reasoning — a large output budget spent in the reasoning channel, finish: "length".
  3. Resume that session.
  4. The prior turn's incomplete reasoning is replayed as a native reasoning part and the model continues it.

OpenCode version

dev @ 1882c33

Expected

Reasoning from a turn that was itself truncated should not be replayed when there is no signature to preserve. Signed reasoning must continue to round-trip unchanged.

Notes

Narrow by intent: only turns whose own finish is "length". A complete reasoning turn replaying is not implicated by this evidence, and cross-model replay already downgrades reasoning to text before this point.

Related but distinct: #40146 covers the session loop treating those truncated turns as normal completions (the same incident, one layer up). #37946 covers empty assistant messages reaching the provider — relevant here because naively dropping reasoning parts can create that shape, which the fix has to avoid.

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