Skip to content

core: SessionRunner sends forced tool_choice ("required"/"none"/named); providers that only support "auto" fail the whole session #45953

Open

Description

@TahaRha

Summary

OpenCode's server-side SessionRunner sends tool_choice values other than "auto" ("none", "required", or named-function choices) on some steps. Providers backed by models/gateways that only implement tool_choice: "auto" (in my case a provider labeled "Console Go") reject these requests with [invalid_request_error], which kills the entire session with "Failed to drain Session" — the subagent/step never completes. OpenCode should either degrade gracefully (retry with "auto"), make forced tool_choice configurable per provider/agent, or at minimum fail a single step instead of the session.

Environment

  • opencode version: 0.0.0-beta-18314 (beta channel; from runtime diagnostics snapshot — --version unavailable in this session)
  • OS: Linux 6.8.0-138-generic (x64)
  • Terminal: Unavailable (no terminal env vars set in this context)
  • Shell: /bin/bash
  • Install/channel: beta
  • Active plugins: none configured (only local/remote MCP servers: github, supabase, vercel, cloudflare)

Reproduction

  1. Configure a custom OpenAI-compatible provider whose backend only supports tool_choice: "auto" (observed with a provider labeled "Console Go").
  2. Run an agent/subagent turn that causes OpenCode to force a tool choice (observed on subagent runs and structured steps).
  3. The provider rejects the request and the session errors out.

Reproduces consistently: the same error appeared across multiple sessions on 2026-08-26 and 2026-08-27 (8+ occurrences in the server log).

Expected Behavior

The session/step should complete. If a provider doesn't support the requested tool_choice mode, OpenCode should fall back to "auto" (possibly with a warning), or the behavior should be configurable per provider/agent rather than hard-forced.

Actual Behavior

Session fails with:

Failed to drain Session
cause="AI.Error: RequestExecutor.execute: Error from provider (Console Go):
Upstream request failed: [invalid_request_error] only `"auto"` is supported for
`tool_choice`. `"none"`, `"required"`, and named function choices are not currently
supported"
at SessionRunner.callModel → SessionRunner.runSteps → SessionRunner.drain

Additional Context

Activity

  1. awesomedog213 commented on Sep 30, 2026

    @awesomedog213

    Adding a concrete, fully reproducible instance of this on the OpenCode Go gateway (opencode-go, https://opencode.ai/zen/go/v1), triggered by the steps agent cap.

    Trigger confirmed: steps cap forces tool_choice: "none"

    The SessionRunner.runStep path sets toolChoice: "none" as soon as step >= agent.info.steps, and that request is what gets rejected. Nothing else in the session needs to be special; any agent with a steps value will eventually fail on its final forced-text step if its model only supports "auto".

    Repro

    Agent config:

    model: opencode-go/muse-spark-1.3-contributor
    steps: 6

    Run any task that needs ~5 tool iterations. Result: 5 assistant turns complete with finish: tool-calls, then the 6th (forced text-only) turn fails:

    AI.Error.InvalidRequest: only `"auto"` is supported for `tool_choice`. `"none"`, `"required"`, and named function choices are not currently supported
    

    Observed deterministically in 3 worker subagent sessions (ses_f0baf501fffeFxVmg09j2gy969, ses_f0baee3a9ffecMWv0Ix06lgazG, ses_f0c062aa4ffeEFiXb1ADz2Iqbb) on 0.0.0-beta-18866: each session has exactly 5 assistant messages ending tool-calls, followed by one assistant message with finish: "error", error.type: "provider.invalid-request", and the message above. The child session then shows idle_outcome: failed and the parent gets a failed subagent tool result instead of findings.

    Minimal gateway repro (no OpenCode client involved)

    POST https://opencode.ai/zen/go/v1/responses with header x-opencode-session: ses_<hex>:

    {
      "model": "muse-spark-1.3-contributor",
      "input": [{"role": "user", "content": "hi"}],
      "tools": [{"type": "function", "name": "noop", "description": "no op", "parameters": {"type": "object", "properties": {}}}],
      "tool_choice": "none",
      "max_output_tokens": 16,
      "stream": false
    }
    • tool_choice: "none" -> 400 {"error":{"message":"only "auto"is supported fortool_choice...","param":"tool_choice","type":"invalid_request_error"}}
    • tool_choice: "required" -> same 400
    • tool_choice: "auto" -> 200

    Same probe with gpt-6-luna, gpt-5.6-luna, deepseek-v4-pro, deepseek-v4-flash, mimo-v2.6-flash, hy3 and longcat-2.5-preview-free returns 200, so this is specific to the Muse Spark contributor endpoints (muse-spark-1.2-contributor rejects it too). The worker model that hits this is @ai-sdk/openai-backed and routed over the Responses protocol.

    Impact

    This makes the documented final-step behavior ("remove tools, ask model to summarize") unusable on Muse Spark, which is otherwise a very cost-effective model. The failure discards a whole completed session's work rather than degrading, and since subagents commonly run with steps caps, it produces silent delegation failures.

    Suggested direction (same as the issue summary)

    • Track supported tool_choice values per model/gateway and fall back to "auto" (or omit tools + tool_choice entirely, the way the Anthropic path already lowers "none") when a provider doesn't support forced choices.
    • Failing that, make the final-step downgrade survive a provider rejection (per-step retry/degrade) instead of killing the drain.
    • On the gateway side, accepting "none" for Muse Spark would also resolve it.

    Note: #51906 reported the same Zen-gateway symptom and was auto-closed by the compliance bot despite matching this issue; it may be worth folding that repro in here.

  2. opencodejr commented on Oct 28, 2026

    @opencodejr
    Contributor

    Thanks for the report. I reproduced this on 2.2.0 (the latest release; 0.0.0-beta-18314 isn't published on npm) and it still happens on the latest dev (506ac20).

    When it happens: in v2 the session runner sends a non-auto tool_choice in one place. Once an agent reaches its steps: limit, the final step sends tool_choice: "none" so the model writes a text summary. Nothing in v2's session code sends "required" or a named choice. A provider that only accepts "auto" rejects that last request, so the run fails instead of summarizing.

    Setup:

    • A mock OpenAI-compatible provider that, like your gateway, returns 400 invalid_request_error for any tool_choice other than "auto" and otherwise always answers with a tool call.
    • The build agent set to steps: 2.
    • opencode run "list files repeatedly".

    What I saw: step 1 is sent without tool_choice and its tool call runs. The final step is sent with tool_choice: "none", the provider rejects it, and the run ends with:

    Error: only `"auto"` is supported for `tool_choice`. `"none"`, `"required"`, and named function choices are not currently supported
    

    The script below reproduces it.

    Workaround: if your agents or subagents set steps: (or maxSteps:), removing or raising that limit should avoid the forced "none" request. The other option is a model whose provider accepts the full tool_choice range.

    Related: #54772 and #51906 report the same error when a subagent or agent hits its steps limit.

    Reproduction script (repro.sh)
    #!/usr/bin/env bash
    # Reproduces #45953: once an agent reaches its `steps:` limit, the session runner sends tool_choice "none".
    # A provider that only accepts tool_choice "auto" rejects that request, so the run ends with an error
    # instead of the documented final text summary.
    # Needs: bun, and `opencode` on PATH (bun add -g @opencode/[email protected]). Override with OPENCODE="<cmd>".
    set -euo pipefail
    OPENCODE=${OPENCODE:-opencode}
    WORK=$(mktemp -d)
    mkdir -p "$WORK/proj" "$WORK/home"
    cp "$(dirname "$0")/mock.ts" "$WORK/mock.ts"
    cat > "$WORK/proj/opencode.json" <<'JSON'
    {
      "$schema": "https://opencode.ai/config.json",
      "provider": {
        "autoonly": {
          "npm": "@ai-sdk/openai-compatible",
          "name": "Auto Only Gateway",
          "options": { "baseURL": "http://127.0.0.1:4517/v1", "apiKey": "x" },
          "models": { "auto-only": { "name": "Auto Only", "tool_call": true } }
        }
      },
      "model": "autoonly/auto-only",
      "agent": { "build": { "steps": 2 } }
    }
    JSON
    echo hi > "$WORK/proj/README.md"
    bun "$WORK/mock.ts" > "$WORK/mock.log" 2>&1 &
    MOCK=$!
    trap 'kill $MOCK 2>/dev/null || true' EXIT
    sleep 1
    cd "$WORK/proj"
    HOME="$WORK/home" XDG_DATA_HOME="$WORK/home/data" XDG_CONFIG_HOME="$WORK/home/cfg" \
      timeout 180 $OPENCODE run "list files repeatedly" 2>&1 > "$WORK/run.log" || true; head -20 "$WORK/run.log"
    echo "--- requests seen by the auto-only mock provider ---"
    cat "$WORK/mock.log"

    Run it: bash repro.sh

  3. hunlight5781-droid commented on Oct 29, 2026

    @hunlight5781-droid

    Thanks for the report. I reproduced this on 2.2.0 (the latest release; 0.0.0-beta-18314 isn't published on npm) and it still happens on the latest dev (506ac20).

    When it happens: in v2 the session runner sends a non-auto tool_choice in one place. Once an agent reaches its steps: limit, the final step sends tool_choice: "none" so the model writes a text summary. Nothing in v2's session code sends "required" or a named choice. A provider that only accepts "auto" rejects that last request, so the run fails instead of summarizing.

    Setup:

    • A mock OpenAI-compatible provider that, like your gateway, returns 400 invalid_request_error for any tool_choice other than "auto" and otherwise always answers with a tool call.
    • The build agent set to steps: 2.
    • opencode run "list files repeatedly".

    What I saw: step 1 is sent without tool_choice and its tool call runs. The final step is sent with tool_choice: "none", the provider rejects it, and the run ends with:

    Error: only `"auto"` is supported for `tool_choice`. `"none"`, `"required"`, and named function choices are not currently supported
    

    The script below reproduces it.

    Workaround: if your agents or subagents set steps: (or maxSteps:), removing or raising that limit should avoid the forced "none" request. The other option is a model whose provider accepts the full tool_choice range.

    Related: #54772 and #51906 report the same error when a subagent or agent hits its steps limit.

    Reproduction script (repro.sh)

    Hello, could u advice exactly where do I rise/set the steps: or maxsteps: coz this one got very annoying recently after updating to 2.2 getting it ever 10-20 minutes or so.

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions