Skip to content

Requests ending with a model turn are not supported #45359

Closed

Description

@Shra1V32

Description

Provider: Google Gemini API (AI Studio)
Model: Gemini 3.7 Flash
issue: Requests ending with a model turn are not supported

happens when the model is usually overloaded, and opencode's internal retry mechanism doesn't handle this edge case.

When does it happen? after the model finishes thinking.

Since when was this happening? Ver. 1.18.21 or above

Plugins

No

OpenCode version

1.18.23

Steps to reproduce

  1. Choose Google Gemini API, Use any gemini model (we'll choose gemini 3.7 flash)
  2. Try to normally user the model
  3. You'll observe after a few turns, the exact issue: Bad Request: {"detail":{"error":{"code":400,"message":"Requests ending with a model turn are not supported.","status":"INVALID_ARGUMENT"}}}
  4. You'lll again have to input prompt to be able to continue

Screenshot and/or share link

No response

Operating System

No response

Terminal

No response

Activity

  1. ianstrom-davinci commented on Sep 4, 2026

    @ianstrom-davinci

    The cause looks like the step-limit prompt being appended as an assistant message, which makes the request end on a model turn.

    Both request-building paths do this. packages/opencode/src/session/prompt.ts:1281 (AI SDK path):

    ...(isLastStep ? [{ role: "assistant" as const, content: MAX_STEPS_PROMPT }] : []),

    and packages/core/src/session/runner/llm.ts:218 (native path, :225 on v2):

    ...(isLastStep ? [Message.assistant(MAX_STEPS_PROMPT)] : [])

    Assistant prefill works on Anthropic but Gemini has no equivalent and rejects it. Reproduced against gemini-3.8-flash with identical history, only the last turn's role differing:

    contents ending on a model turn -> 400 INVALID_ARGUMENT: Requests ending with a model turn are not supported.
    contents ending on a user turn  -> 200
    

    That also explains why it mostly shows up on subagents: steps has no default, so it only triggers on agents that configure it.

    Sending that prompt as a user turn fixes it. I checked the instruction still lands — the model replies with the "maximum steps reached" summary it asks for — and tools are already disabled for that step via toolChoice: "none", so the constraint isn't relying on the message role. It's also consistent with how the runner already injects synthetic input, compaction checkpoints and chronological system updates, which all ride in user turns.

    I have this working on both dev and v2 with the suites passing. Happy to open a PR if useful — which branch would you prefer? #40455 looks like the same prefill causing a 400 on Claude 4.6+, so a fix here would likely cover that too.

  2. Shra1V32 commented on Sep 4, 2026

    @Shra1V32
    Author

    Thanks for the thorough investigation @ianstrom-davinci! You're spot on regarding the root cause with MAX_STEPS_PROMPT and trailing assistant turns triggering 400s across Gemini (and Claude 4.6+ in #40455).

    We are addressing this by moving MAX_STEPS_PROMPT into the system prompt array directly (rather than appending a synthetic user turn or assistant prefill) across both prompt.ts and session/runner/llm.ts. This maintains clean conversation history, keeps the system constraint in system context without altering turn structure, and prevents consecutive-user turn issues when step limits trigger after user input.

    Additionally, we've added Gemini-specific sequence normalization in ProviderTransform to handle edge cases like consecutive role turns, empty turns, and tool-response to user-prompt transitions.

    Opening a PR with this fix shortly!

  3. vuhnger commented on Sep 15, 2026

    @vuhnger

    Still hitting this on version 1.18.31.

    Log excerpts:

    timestamp=2026-09-15T15:50:02.480Z level=ERROR run=[REDACTED] message="stream error" providerID=google modelID=gemini-3.8-flash session.id=[REDACTED] small=false agent=build mode=primary error.error="AI_APICallError: Requests ending with a model turn are not supported."
    timestamp=2026-09-15T15:50:02.483Z level=ERROR run=[REDACTED] message=process session.id=[REDACTED] messageID=[REDACTED] error="Requests ending with a model turn are not supported." stack="AI_APICallError: Requests ending with a model turn are not supported.\n    at <anonymous> (/$bunfs/root/chunk-yhh8qky9.js:6:14258)\n    at async <anonymous> (/$bunfs/root/chunk-yhh8qky9.js:6:12506)\n    at processTicksAndRejections (native:7:39)"
    timestamp=2026-09-15T15:50:20.429Z level=ERROR run=[REDACTED] message="stream error" providerID=google modelID=gemini-3.8-flash session.id=[REDACTED] small=false agent=build mode=primary error.error="AI_APICallError: Requests ending with a model turn are not supported."
    timestamp=2026-09-15T15:50:20.430Z level=ERROR run=[REDACTED] message=process session.id=[REDACTED] messageID=[REDACTED] error="Requests ending with a model turn are not supported." stack="AI_APICallError: Requests ending with a model turn are not supported.\n    at <anonymous> (/$bunfs/root/chunk-yhh8qky9.js:6:14258)\n    at async <anonymous> (/$bunfs/root/chunk-yhh8qky9.js:6:12506)\n    at processTicksAndRejections (native:7:39)"
    timestamp=2026-09-15T15:52:34.656Z level=ERROR run=[REDACTED] message="stream error" providerID=google modelID=gemini-3.8-flash session.id=[REDACTED] small=false agent=build mode=primary error.error="AI_APICallError: Requests ending with a model turn are not supported."
    timestamp=2026-09-15T15:52:34.660Z level=ERROR run=[REDACTED] message=process session.id=[REDACTED] messageID=[REDACTED] error="Requests ending with a model turn are not supported." stack="AI_APICallError: Requests ending with a model turn are not supported.\n    at <anonymous> (/$bunfs/root/chunk-4h8zz3st.js:6:14258)\n    at async <anonymous> (/$bunfs/root/chunk-4h8zz3st.js:6:12506)\n    at processTicksAndRejections (native:7:39)"
    timestamp=2026-09-15T15:55:15.873Z level=ERROR run=[REDACTED] message="stream error" providerID=google modelID=gemini-3.8-flash session.id=[REDACTED] small=false agent=build mode=primary error.error="AI_APICallError: Requests ending with a model turn are not supported."
    timestamp=2026-09-15T15:55:15.877Z level=ERROR run=[REDACTED] message=process session.id=[REDACTED] messageID=[REDACTED] error="Requests ending with a model turn are not supported." stack="AI_APICallError: Requests ending with a model turn are not supported.\n    at <anonymous> (/$bunfs/root/chunk-4h8zz3st.js:6:14258)\n    at async <anonymous> (/$bunfs/root/chunk-4h8zz3st.js:6:12506)\n    at processTicksAndRejections (native:7:39)"
    
  4. added 2 commits that reference this issue on Sep 24, 2026
    d1db616
    7bbbd86
  5. brendandebeasi commented on Oct 6, 2026

    @brendandebeasi

    There is a second trigger for this beyond MAX_STEPS_PROMPT: silent safety/policy suppressions where Gemini emits reasoning tokens but zero output tokens.

    We hit this repeatedly on gemini-3.8-flash in v1.18.32 during code audits and security/auth reviews:

    1. Gemini runs its thinking/reasoning pass (emitting 200–1500 reasoning tokens).
    2. During internal safety/risk classification, the provider halts generation before generating candidate text or tool calls (output: 0, finish: "unknown").
    3. The runner treats this empty output as an incomplete turn and loops to step=1.
    4. The history serialized for step=1 ends on the empty assistant/reasoning turn. Gemini immediately rejects it with HTTP 400 (Requests ending with a model turn are not supported.).

    Regarding #51104: blindly appending a synthetic user Continue. message when the trailing turn is assistant won't fix this scenario. If the provider suppressed the output due to content policy, prompting Continue. will just trigger another empty response or loop.

    The runner needs to detect when a Gemini turn completes with reasoning tokens but zero candidate output/tools, and surface that the response was withheld (e.g. content/safety filter triggered) instead of cascading into a continuation loop or a 400 error.

  6. rekram1-node commented on Oct 6, 2026

    @rekram1-node
    Contributor

    Shouldn't be possible on v2: https://opencode.ai/v2/docs

    Also blocked responses are recognized and halted on, they will never auto continue unless it is a completely custom provider that doesnt send an existing recognized stop reason.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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