Skip to content

session: upstream rejects with missing 'reasoning_content' on older assistant message after model/provider switch #55096

Open

Description

@CTKiet2006

Summary

A long-running session started failing with [missing_required_parameter] Missing required parameter: 'messages[100].reasoning_content' after a mid-session provider.updated/model.updated event. The upstream provider now requires reasoning_content for every earlier assistant message, but one message in history (index 100) has none, so every subsequent request in the session is rejected.

Environment

  • opencode version: 2.0.28
  • OS: Windows_NT 10.0.26200 (win32 x64)
  • Terminal: TERM=xterm-256color (TERM_PROGRAM unset)
  • Shell: C:\WINDOWS\system32\cmd.exe
  • Install/channel: release (latest)
  • Active plugins: none found in config (only an MCP server blender-mcp configured)

Reproduction

  1. Start a build session on a reasoning model from the opencode provider (session was at ~100 messages).
  2. Let a tool call get interrupted mid-turn (a Start-Sleep foreground shell command hit the 120s tool timeout and was interrupted).
  3. Shortly after, an automatic provider.updated + model.updated event fires (polling refresh, every ~5 min in this log).
  4. The next continuation request fails with the error below; retries fail the same way.

Expected Behavior

Session continues normally; either OpenCode preserves/replays reasoning_content for prior assistant messages, or it strips/normalizes history to match the active model's requirements.

Actual Behavior

Two consecutive errors in the log, then the session was only usable again after a later provider/model refresh:

timestamp=2026-10-13T09:21:43.610Z level=ERROR run=23ed6170 message="Failed to drain Session" cause="AI.Error: Upstream request failed: [missing_required_parameter] Missing required parameter: 'messages[100].reasoning_content'. This model needs the reasoning of every earlier assistant message: send back the 'reasoning_content' returned with that message."
timestamp=2026-10-13T09:21:54.968Z level=ERROR run=23ed6170 message="Failed to drain Session" (same error)

Related events right before the failure:

09:19:07 event.type=provider.updated
09:19:07 event.type=model.updated

Note: an interrupted tool call occurred at 09:19 (Tool execution interrupted for a foreground shell command), possibly leaving a truncated assistant message without reasoning content at history index 100.

Additional Context

  • Frequency: twice in one session (09:21:43 and 09:21:54), triggered consistently until the session state moved past the bad message.
  • Workaround: starting a new session avoids the issue.
  • The session had ~100 messages, so older messages were likely written by a different model/provider revision before the refresh.
  • Hypothesis: message history persisted without reasoning_content + a provider that strictly requires it for all earlier assistant messages = unrecoverable session. OpenCode could either persist reasoning content for reasoning models or sanitize history when the active model requires it.

Activity

  1. opencode-agent commented on Oct 13, 2026

    @opencode-agent
    Contributor

    Thanks for the detailed report. I was able to reproduce this on 2.0.28, and it still happens on the latest dev (4ffede4).

    What I did, using a simulated OpenAI-compatible thinking model that sends reasoning back in reasoning_content:

    1. One normal turn with reasoning and a reply.
    2. A turn where the model reasons and then starts a foreground shell command, which is interrupted ("Tool execution interrupted").
    3. A follow-up message, "continue".

    In the request for step 3, the completed turn is sent with its reasoning_content. The interrupted assistant message is sent with an empty reasoning_content, and its reasoning text is moved into the normal message content. A provider that needs the reasoning of every earlier assistant message rejects that, which matches your Missing required parameter: 'messages[N].reasoning_content' error. Because the same message is sent again on every request, the session stays broken. No provider or model switch was needed, so the provider.updated / model.updated events in your log were probably a coincidence. The interrupted tool call is what triggers it.

    The recording and screenshot below show the run, and the script below reproduces it.

    Related: #38620 is the same underlying behaviour hitting Anthropic thinking, and #54693 is an open PR in this area. Starting a new session remains the workaround for now.

    recording.mp4

    Screenshot

    Reproduction script (drive.ts)
    // Repro for #55096: an interrupted assistant turn (reasoning + shell tool call)
    // is replayed on the next request without its reasoning (reasoning_content),
    // which providers that require reasoning on every earlier assistant message reject.
    import { Effect, Stream } from "effect"
    import { appendFileSync, writeFileSync } from "node:fs"
    import { Llm, OpenCodeDriver } from "opencode-drive"
    
    const LOG = "/workspace/repro/requests.jsonl"
    writeFileSync(LOG, "")
    
    const describe = (body: any) => {
      const msgs: any[] = body?.messages ?? body?.input ?? []
      return msgs
        .map((m, i) => ({ i, m }))
        .filter(({ m }) => m.role === "assistant")
        .map(({ i, m }) => {
          const has = typeof m.reasoning_content === "string" && m.reasoning_content.length > 0
          return `messages[${i}] reasoning_content=${has ? "present" : "MISSING"}`
        })
    }
    
    export default OpenCodeDriver.use(
      {
        ...(process.env.OC_DEV ? { opencode: { dev: process.env.OC_DEV } } : {}),
        tui: { recording: true },
        tools: ["shell"],
        // Make the simulated OpenAI-compatible model behave like a DeepSeek/Kimi-style
        // thinking model on Zen: reasoning is carried in `reasoning_content`.
        config: {
          providers: {
            simulation: {
              models: { "gpt-sim-model": { compatibility: { reasoningField: "reasoning_content" } } },
            },
          },
        } as any,
      },
      ({ ui, llm, tools }) =>
        Effect.gen(function* () {
          const shells = yield* tools.control("shell")
          yield* llm.serve((request, index) => {
            appendFileSync(LOG, JSON.stringify({ index, url: request.url, body: request.body }) + "\n")
            // Turn 1: a normal completed reasoning turn (control).
            if (index === 0)
              return Stream.make(Llm.reasoning("The user says hi."), Llm.text("Hello!"))
            // Turn 2: reasoning + a foreground shell call that will be interrupted.
            if (index === 1)
              return Stream.make(
                Llm.reasoning("I should wait for the build with a sleep command."),
                Llm.toolCall({ index: 0, id: "call_sleep", name: "shell", input: { command: "Start-Sleep 600", description: "wait" } }),
                Llm.finish("tool-calls"),
              )
            const lines = describe(request.body)
            const missing = lines.some((l) => l.includes("MISSING"))
            return Stream.make(
              Llm.reasoning("Checking history."),
              Llm.text(
                (missing
                  ? "UPSTREAM WOULD REJECT: Missing required parameter reasoning_content. "
                  : "OK: every assistant message has reasoning_content. ") + lines.join("; "),
              ),
            )
          })
          yield* ui.submit("hi")
          yield* ui.waitFor("Hello!")
          yield* ui.submit("Run the build and wait for it")
          const shell = yield* shells.take("call_sleep")
          yield* shell.progress("Sleeping...\n")
          yield* Effect.sleep(1500)
          // Interrupt the turn while the foreground shell is running.
          yield* ui.press("escape")
          yield* Effect.sleep(300)
          yield* ui.press("escape")
          yield* Effect.sleep(2000)
          yield* ui.screenshot("interrupted")
          yield* ui.submit("continue")
          yield* ui.waitFor("reasoning_content", { timeout: 20000 } as any)
          yield* Effect.sleep(1000)
          yield* ui.screenshot("result")
        }),
    )

    Run it with opencode-drive: opencode-drive run ./drive.ts. It drives opencode from your PATH; set OC_DEV to the path of an OpenCode checkout to run that from source instead.

  2. JonnieDoe commented on Nov 3, 2026

    @JonnieDoe

    Same error on v2.2.5 (macOS 27.2.0 arm64, tmux/zsh, channel: latest), this time at messages[20]:

    Error: Missing required parameter: 'messages[20].reasoning_content'. This model needs the reasoning of every earlier assistant message: send back the 'reasoning_content' returned with that message. If 'reasoning_content' is not available, consider starting a new session or conversation history.
    

    Provider: custom OpenAI-compatible endpoint (reflection.ai, via @ai-sdk/openai-compatible), model Beam-1.0-501B-A23B — a thinking model served with DeepSeek-style reasoning_content semantics.
    Context:

    • No deliberate turn interruption and no mid-session model/provider switch were involved; the error appeared during ordinary chat/tool use, so the offending message may come from another path that drops reasoning_content (e.g. aborted/errored turns, compaction). Trigger unclear.
    • The session became unusable: every subsequent request failed with the same error. Starting a new session fixed it (matches the documented workaround).
    • Plugins active in the affected session: opencode-mem, @dietrichgebert/ponytail, superpowers, oh-my-opencode-slim, envsitter-guard (local), @tarquinen/opencode-dcp, @openchamber/opencode-claude.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions