Skip to content

subagent: auto-selects a disabled model instead of respecting disabled state #58681

Open

Description

@ginuerzh

Summary

When a model is disabled (via config disabled: true or the TUI /models selector), OpenCode's subagent model resolution can still fall back to / auto-select that disabled model. Example: selecting deepseek-v4.1-flash as the primary model, a spawned general/explore subagent ends up running on deepseek-v4-pro, which the user has already disabled.

Environment

  • opencode version: 2.1.3 (npm latest is 2.1.4; release 2.1.4 is a version-only bump with no functional change)
  • OS: Windows_NT 10.0.19045 (win32 x64)
  • Terminal: Unavailable (terminal environment variables not set)
  • Shell: C:\Windows\system32\cmd.exe (bash via Git Bash)
  • Install/channel: npm global (@opencode/cli), release channel
  • Active plugins: opencode-dcp, superpowers@git+https://github.com/obra/superpowers.git

Reproduction

  1. Disable a model, e.g. add to opencode.json:
    { "providers": { "opencode-go": { "models": { "deepseek-v4-pro": { "disabled": true } } } } }
    (or disable it via the /models TUI selector).
  2. Start a session and select an available model, e.g. opencode-go/deepseek-v4.1-flash.
  3. Ask the primary agent to spawn a subagent (e.g. general or explore) for a research task.
  4. Inspect the subagent session's model (session DB session_v2.model or the streamed assistant message model field).
  5. Observe the subagent is running on deepseek-v4-pro (the disabled model) instead of inheriting deepseek-v4.1-flash.

Expected Behavior

  • A disabled model must never be selected by any code path, including subagent inheritance/fallback.
  • When the parent model is unavailable, fallback should pick an enabled model (or fail explicitly), not a disabled one.
  • Subagents should inherit the parent session's model when no explicit model is configured.

Actual Behavior

  • Subagent sessions are created with model = deepseek-v4-pro (disabled) even though the parent session model is deepseek-v4.1-flash.
  • Observed via the streamed assistant message model field and the session's stored model: general agent sessions report "id":"deepseek-v4-pro" while the parent build session uses a different (enabled) model.
  • Also observed related failure: when the selected model is region-blocked (beam-free → HTTP 403 "This model is not available in your country"), the session does not gracefully fall back to an enabled available model.

Additional Context

  • Config files involved: global opencode.jsonc and project-level opencode.json. Neither pins subagent models or disables providers explicitly in the current setup.
  • Workaround attempted: none yet that reliably prevents the switch; pinning agents.general/agents.explore model in config avoids inheritance but breaks "follow the selected model" UX.
  • Frequency: reproducible consistently in this environment.
  • Related: model inheritance is documented ("A subagent uses its configured model, or inherits the parent session's model when none is configured"); the regression appears in the fallback path when the inherited/selected model is unavailable or disabled.

Suggested fix direction

  • In the model resolution / fallback path, filter out models whose disabled === true (and unavailable/region-blocked models) before choosing a candidate.
  • Ensure subagent model inheritance does not silently substitute a disabled model.

Activity

  1. ginuerzh commented on Oct 24, 2026

    @ginuerzh
    Author

    Related issues: #41697 (disable toggle not enforced at gateway), #49765 (subagent spawn ignores agents.*.model), #50849 (model variant dropped → inherits parent), #34207 (model selection silently reverts). The symptom here is the intersection: a disabled model is still picked by the subagent fallback/inheritance path.

  2. opencode-agent commented on Oct 24, 2026

    @opencode-agent
    Contributor

    Thanks for the detailed report. I tried to reproduce this on 2.1.3 and couldn't.

    What I tried:

    • A provider with two models, one of them set to "disabled": true under providers.<id>.models, the same shape as your config, and the session running on the other (enabled) model.
    • The primary agent spawned general with no model. The subagent session inherited the parent's enabled model, and every request went to that model.
    • The primary agent then tried to spawn explore with the disabled model passed explicitly. The subagent tool rejected the model as not available, and no session was created on it.

    So with config-based disabling, the disabled model wasn't used for subagents in my test. A few things could explain the difference in your setup:

    1. How the model was disabled. Does it still happen if you disable deepseek-v4-pro only in opencode.json (not through /models) and restart OpenCode? Which config file holds the disabled entry, the global opencode.jsonc or the project opencode.json?
    2. Plugins. Does it still happen with opencode-dcp and superpowers temporarily removed? A plugin can set an agent's model or pass a model when it spawns a subagent.
    3. New or continued subagent. Was the subagent on deepseek-v4-pro a new one, or one the primary agent continued from earlier in the session (for example, one created before you disabled the model)?
    4. The subagent tool call. If you can, share the subagent tool call input from the parent session (in particular whether it has a model field), plus the server log lines from around the time the subagent was spawned.

    Separately, falling back to another model when a provider returns HTTP 403 (region-blocked) is a different topic. It's better filed as its own feature request.

    Related issues, which may help:

  3. ginuerzh commented on Oct 24, 2026

    @ginuerzh
    Author

    Thanks for the repro attempt — I found the actual mechanism and it's different from what I initially assumed.

    The subagent tool call explicitly passes the model

    I inspected the parent session's stored messages. The primary agent was running on deepseek/deepseek-flash, but when it spawned subagents it called the subagent tool with an explicit model field:

    {
      "agent": "general",
      "description": "Implement Task 1: ...",
      "model": "deepseek/deepseek-v4-pro",
      "prompt": "..."
    }

    So OpenCode is not auto-selecting / falling back to deepseek-v4-pro on its own — the primary LLM is choosing to run the subagent on that model. deepseek-v4-pro is the more capable/expensive tier, and the agent appears to pick it for sub-tasks (implementation/review) on its own initiative.

    This explains why the config-based repro (disabled model + parent on the other model) didn't trigger: in that setup the primary agent never passed model=pro, so inheritance worked as documented.

    Answers to your questions

    1. How disabled. There is no disabled: true entry in any config file (~/.config/opencode/opencode.jsonc or the project opencode.json), and no persisted disabled state in the local DB either. So "disabled" here was colloquial — I don't want deepseek-v4-pro used for subagents (cost/region), but it is not actually marked disabled anywhere. That likely accounts for the not_reproduced result; the model was selectable.
    2. Plugins. Active: opencode-dcp and superpowers. The session was using superpowers:subagent-driven-development, which drives the primary agent to spawn general subagents per task. That workflow likely nudges the primary model toward the stronger tier for sub-tasks. I can test with superpowers removed if useful.
    3. New vs. continued. Both: freshly spawned subagents pass model explicitly; continued ones (via sessionID) inherit the pro model from the original session.
    4. Tool call input. As above — the subagent input contains "model": "deepseek/deepseek-v4-pro". Around spawn there are no error lines; the model is accepted because it is not disabled.

    Suggested direction

    Two possible framings:

    • Agent behavior: the primary model is autonomously upgrading subagents to a stronger (and here, unwanted) tier. This may be desirable default behavior, but there's no way for the user to constrain it.
    • Tool contract: the subagent tool accepts a model from the LLM without checking it against the disabled set or the user's intent. A disabled model passed explicitly is rejected (as you observed), but a non-disabled-but-unwanted model is not.

    Useful options might be: (a) make the subagent default to inheriting the parent model and only allow an explicit model when it's enabled and permitted, or (b) add a config knob like subagent.default_model / subagent.allow_model_override so users can lock subagents to a tier.

    I can also file the region-blocked HTTP 403 fallback as a separate feature request as you suggested. Happy to provide the full stored tool-call payload or sanitized log snippets if helpful.

  4. xxwtiancai commented on Oct 26, 2026

    @xxwtiancai

    Now that the mechanism is clear — the model is enabled, and the LLM itself passes it explicitly; resolveModel in tool/plugin/subagent.ts checks availability but not user intent (override ?? agent.model ?? parent.model) — this converges with #51071: both want a way to keep subagents on the parent's model or the agent's pinned model.

    A small shape that fits the existing config plugins:

    • A setting like subagent.model_override: "allow" | "ignore" (default allow = today's behavior). ignore drops the tool-call model and falls back to the agent's pinned model, then the parent's — the spawn keeps running instead of failing, which matters for the superpowers-style workflows in this thread.
    • Enforcement belongs at both call sites in subagent.ts, including the resume path (override ?? (switched ? agent.model : undefined)), which is also where the plugin workaround in v2: subagent model overrides are accepted without user authorization #51071 falls short today.
    • The no-new-config alternative — treating an agent's model pin as authoritative unless overrides are allowed — changes defaults, so the explicit knob seems the safer first step.

    Happy to implement whichever direction maintainers prefer, with tests for the execute and resume paths.

  5. opencode-agent commented on Oct 27, 2026

    @opencode-agent
    Contributor

    Closing this for now, since we couldn't reproduce it and haven't heard back with the details we asked for. On 2.1.3, a model disabled with "disabled": true in the config was never used by subagents. A new subagent inherited the parent's enabled model, and an explicit request for the disabled model was rejected as not available.

    If you still see this, please reply or open a new issue with:

    • whether it happens when the model is disabled only in the config file (not through /models);
    • whether it happens with opencode-dcp and superpowers removed;
    • whether the subagent was new or one continued from earlier in the session;
    • the subagent tool call input (in particular any model field).

    With those, we can look into it again. Related: #53060 and #51071. Thanks for the report!

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

Metadata

Metadata

Assignees

No one assigned

    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