Skip to content

Config tool_call: false does not disable tools #35432

Open

Description

@tobwen

Description

tool_call: false in model config is ignored by the prompt loop. The code unconditionally resolves SessionTools and sends them in the LLM request body, with tool_choice defaulting to "auto". Providers that serve models without tool-call support (e.g. morphllm without --enable-auto-tool-choice) reject the request with a 400 error.

Root cause

In packages/opencode/src/session/prompt.ts, the SessionTools.resolve() call and the toolChoice parameter in handle.process() both ignore model.capabilities.toolcall. Setting tool_call: false in model config correctly sets capabilities.toolcall to false in packages/opencode/src/provider/provider.ts:1216, but the prompt loop never reads that field.

Why this is bad

The user explicitly opts out of tool calls via tool_call: false, expecting a model-safe request with no tools sent. Instead the prompt loop sends a full tools array with tool_choice: "auto", which providers reject if they do not support tool calling at all. There is no workaround at the session or request level -- the config field is effectively a no-op.

Steps to reproduce

  1. Configure a model with tool_call: false:

    {
      "provider": {
        "morphllm": {
          "models": {
            "morphllm/Qwen3-30B-A3B": {
              "tool_call": false
            }
          }
        }
      }
    }
  2. Start a session with that model and send a message.

  3. The prompt loop calls the provider API with "tools": [...] and "tool_choice": "auto", even though tool calls are disabled.

  4. Providers that require --enable-auto-tool-choice and --tool-call-parser flags return HTTP 400.

Expected

With tool_call: false, the LLM request body should contain neither tools nor tool_choice keys. The prompt loop should skip SessionTools.resolve() entirely.

OpenCode version

dev (current)

Operating System

Linux

Activity

  1. github-actions commented on Jul 5, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

  2. sbadakhc commented on Jul 9, 2026

    @sbadakhc

    Confirming this on opencode 1.17.15 (linuxbrew binary) against NVIDIA's hosted NIM endpoint (integrate.api.nvidia.com/v1, model nvidia/nemotron-3-content-safety). The endpoint rejects any request carrying tools with vLLM's "auto" tool choice requires --enable-auto-tool-choice and --tool-call-parser to be set, and tool_call: false in the model config does not stop opencode from sending them.

    Independent confirmation of the root cause described above: grepping the bundled binary shows capabilities.toolcall is computed during config/catalog merge (toolcall:f.tool_call??_?.capabilities.toolcall??!0) but never read anywhere in the request path, so the config field is a no-op for outgoing requests.

    Working user-level workaround until this is fixed: define an agent with the legacy tools wildcard map, which does prevent tool definitions from being sent:

    {
      "agent": {
        "safety": {
          "mode": "primary",
          "model": "nvidia/nvidia/nemotron-3-content-safety",
          "tools": { "*": false }
        }
      }
    }

    Verified working end-to-end with opencode run --agent safety -- the request goes out with no tools and the endpoint responds normally.

    Note for anyone trying the recommended permission field instead: permission: { "...": "deny" } does not help here. It blocks tool execution but the tool definitions are still included in the request body, so providers that reject tool-bearing requests still fail.


    • Agent: Claude Code (Claude Fable 5, model id claude-fable-5)
    • Date: 2026-07-09
    • Method: log analysis, binary inspection of the opencode bundle, live API probes against the affected endpoint
    • Scope: user's local opencode config and logs; no access to opencode source checkout
    • Confirmation: no (no code changes; comment only)

  3. IngTian commented on Aug 10, 2026

    @IngTian

    There's a fix up for this now: #41463.

    I hit the same root cause from a different angle — Vertex Gemini image models (gemini-2.5-flash-image) reject every request with Unable to submit request because the model does not support function calling — and filed #41464 before the duplicate bot pointed me here. Same cause you diagnosed: capabilities.toolcall is assigned in provider.ts and never read in the request path.

    The patch is a guard in resolveTools (session/llm/request.ts), which is where every send path converges:

    if (!input.model.capabilities.toolcall) return {}

    Verified by capturing the outgoing provider request from a source build with the stock build agent and no config overrides: a toolcall: false model now sends no tools field at all (was 12 declarations), while a tool-calling model in the same session still sends all 12. Two tests added; I checked the negative one fails without the guard.

    Worth noting this is the third report of the same thing — #19966 (closed by the 60-day inactivity bot rather than on merit), this issue, and #41464. @sbadakhc's morphllm/NVIDIA NIM cases should be covered by the same guard, since it drops the tools array before any provider-specific serialization.

    Happy to fold #41464 into this issue if maintainers prefer one thread.

  4. TohaSlovo commented on Sep 13, 2026

    @TohaSlovo

    placeholder

  5. yesvus commented on Sep 20, 2026

    @yesvus

    Still live on v2.0.10 (channel latest, Linux arm64, stock build agent, no plugins).

    This is not limited to a hand-written tool_call: false. It reproduces straight from the models.dev catalog: z-ai/glm-5.2:free and nvidia/nemotron-3.5-content-safety:free both carry tool_call: false, yet OpenCode still sends an edit tool definition. OpenRouter answers with a routing error instead of a completion, so the model is a trap: it is offered in /models, then every tool-using turn fails.

    Repro:

    opencode run --model openrouter/z-ai/glm-5.2:free "Reply with the single word OK and nothing else."
    Error: No endpoints found that support tool use. Try disabling "edit". To learn more about provider routing, visit: https://openrouter.ai/docs/guides/routing/provider-selection
    

    That message comes from OpenRouter's "Filter by Tool Compatibility" routing step, which fires only when the request body carries tools, so it is independent proof the outgoing request ignored capabilities.toolcall: false.

    Server log:

    level=ERROR message="Failed to drain Session" cause="Session.StepFailedError: No endpoints found that support tool use. Try disabling \"edit\". ..."
    

    Expected: a model with capabilities.toolcall: false should send neither tools nor tool_choice, or should not be offered to a tool-using agent in the first place. Neither guard exists on v2.

    The earlier fix (#41463, the guard where tools are resolved) was auto-closed before merge and never reached v2, so the flag is still assigned during catalog/config merge and never read on the send path.

    Mitigations in the meantime: disabled: true on the tool-less models, or an agent with tools: { "*": false } as @sbadakhc found.

  6. ssc-esiemiat commented on Oct 18, 2026

    @ssc-esiemiat

    Still broken on v2.0.32 (Windows x64, latest channel, stock build agent, no plugins) against a tool-less model served by our internal vLLM gateway. Model configured with the current capabilities spelling:

    {
      "providers": {
        "ssnc-onprem-us": {
          "models": {
            "zai-org-GLM-OCR": {
              "capabilities": { "tools": false }
            }
          }
        }
      }
    }

    Wire capture shows the agent request still carries the full 12-tool set (edit, glob, grep, question, read, shell, skill, subagent, webfetch, websearch, write, execute) and no tool_choice key at all. OpenAI-compatible servers treat an omitted tool_choice as "auto" — the model is free to pick a tool — and engines with tool calling disabled for a model (like ours, and the other reporters' endpoints) reject any tool-bearing request on exactly that basis with their standard tool-choice 400. Curiously the per-session title request correctly carries no tools and is accepted — only the agent run path regresses. Repro:

    opencode run -m ssnc-onprem-us/zai-org-GLM-OCR "Reply with exactly: OK"
    

    Two additional data points not yet in this thread.

    1. The tools: { "*": false } mitigation no longer works on 2.0.32 — user-defined agents cannot be used at all. Earlier comments suggest moving the model onto an agent whose tool map disables tools, but on this release such agents cannot be selected: --agent ocr fails with "Agent not found" no matter where the agent is defined (config agents/agent keys, ~/.config/opencode/agents/*.md, project .opencode/agents/*.md). Built-in agents still resolve (--agent build works), so selection itself functions — only user-defined agents fail to load. The agent-defining docs are ahead of the CLI here.

    2. Model body nulls are a working per-model workaround on 2.0.32, using null = delete-key merge semantics:

    "zai-org-GLM-OCR": {
      "capabilities": { "tools": false },
      "body": { "tools": null, "tool_choice": null, "parallel_tool_calls": null }
    }

    Verified live: the merged agent request goes out with no tools/tool_choice/parallel_tool_calls keys and the engine answers normally. Two gotchas worth recording: a null deletes the key, but "tools": [] does not — the empty array stays in the body and the engine still returns a 400 validation error, so only null works. It also composes cleanly with other body pins since merge order applies variant-last.

    Would appreciate the capabilities.toolcall guard (the #41463 approach) landing so the explicit capabilities.tools: false assertion becomes effective — the body-null workaround is a stand-in until then.

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