Repository navigation
Description
Note: AI-generated, human-reviewed.
Summary
OpenCode does not carry a model over when work is dispatched to it without an explicit model. POST /api/session accepts an optional model, and when the caller omits it the session is created model-less: the model is resolved later, at the first prompt, from the catalog default. That default is chosen by release date alone, so it does not match the model the user actually works with. In this report it resolved to opencode/jev-1.13-free, a model the user never selected, which additionally declares capabilities.tools: false and whose provider cannot serve this caller. Every prompt in such a session fails, and the caller only sees "outcome": "failed" on the session. The reporter has exactly one usable model enabled, so any resolution to a different catalog entry is necessarily the wrong model.
Environment
- opencode version: v2.0.10
- OS: Windows 10.0.26200.9457
- Install/channel: desktop windows(x64)
Reproduction
- Have the server running (
opencode serveor the shared background service). - Create a session with no
modelin the body:POST /api/sessionwith{}. - Read what such a session resolves to:
GET /api/model/default. It returns the same value for every project directory. - Send a prompt:
POST /api/session/{id}/promptwith{"text":"reply with the single word OK"}. - Inspect
GET /api/session/{id}/message.
Expected Behavior
A session created without an explicit model should run on the model the user works with - the one their own sessions use, or the configured default - rather than on whatever happens to be the newest catalog entry.
At minimum the resolved default should be usable for agent work: tool-capable and actually callable by this user. The models documentation describes the config fallback as "the newest available supported model", and GET /api/model/default is documented as "the model used when a session has no explicit model selection".
The reporter has exactly one usable model enabled. When OpenCode cannot determine a user-level default model, or the model it resolves to is not callable by the user, it should fail with a clear and actionable error that names the resolution step, instead of silently selecting a catalog entry.
Actual Behavior
The user's own sessions all run on opencode-go/deepseek-v4.1-flash#high (for example "model":{"id":"deepseek-v4.1-flash","providerID":"opencode-go","variant":"high"} recorded on each of them). Only sessions created over the API without a model diverge from that.
GET /api/model/default resolves to opencode/jev-1.13-free:
{
"id": "jev-1.13-free",
"modelID": "jev-1.13-free",
"providerID": "opencode",
"name": "Jev 1.13 Free",
"package": "@opencode/ai/providers/openai-compatible",
"capabilities": { "tools": false, "input": ["text"], "output": ["text"] },
"variants": [],
"time": { "released": 1789430400000 },
"status": "active",
"enabled": true
}It is the newest entry in the catalog (GET /api/model is ordered by release date) and, of the 35 entries reported as "enabled": true, the only one with capabilities.tools: false. Newest first: jev-1.13-free (2026-09-15), deepseek-v4.1-flash (2026-09-10), muse-spark-1.3-contributor (2026-09-02), muse-spark-1.3-contributor-free (2026-09-02), ...
Prompting such a session records the resolved model on the assistant message and fails:
{
"type": "assistant",
"agent": "build",
"model": { "id": "jev-1.13-free", "providerID": "opencode" },
"content": [],
"retry": {
"attempt": 5,
"error": { "type": "provider.internal", "message": "Internal server error", "status": 500 }
}
}The opencode provider is not callable by this user at all. Switching the same session to another opencode/* catalog model fails immediately:
{
"type": "assistant",
"model": { "id": "muse-spark-1.3-contributor-free", "providerID": "opencode", "variant": "default" },
"finish": "error",
"error": { "type": "provider.auth", "status": 403 }
}So the provider that supplies the automatic default is one this user cannot call, while GET /api/model still reports its models as "enabled": true.
Additional Context
- The silent substitution is a defect on its own, independent of whether the request fails. If the substituted model happens to be callable, the dispatched task is executed by a model the caller never chose, with different capabilities, context limit, cost and reasoning behavior, and nothing in the session result indicates the swap. For work dispatched from another agent, that silently changes the quality of the task output and makes it unreproducible.
- Impact: anything that dispatches work to OpenCode without pinning a model is affected - external agents and orchestrators, CI, scripts. The session ends with
"outcome": "failed", and the cause (an unusable default model) is several layers down inside the message record, so the caller sees only a failure. - The default is not stable. It changes whenever a newer model enters the catalog.
jev-1.13-freewas released 2026-09-15; before that the newest entry wasopencode-go/deepseek-v4.1-flash(2026-09-10), which this user can run. The breakage therefore started around 2026-09-15 with no local change, and repeated itself after an earlier occurrence. - Workarounds, not fixes: pin the model at creation (
POST /api/sessionacceptsmodel) or afterwards (POST /api/session/{id}/model), or set a rootmodelin config so the catalog default is a known-good model. The config route cannot pin a variant, since per the models docs the V2 catalog default does not retain one. "enabled": truedoes not currently imply "callable by this user": every model of the provider in question is reported as enabled even though this user cannot call any of them.- Possibly related: tui (v2): config default model ignored, falls back to first provider's model #43055 (config default model ignored, falls back to first provider's model), Cold-start default model is stale: substring priority list picks gemini-3-pro-preview over every newer model #39125 (cold-start default model is stale), opencode serve: provider auth (auth.json key) not attached to LLM requests for sessions created via the v2 API (/api/session) — works on v1 (/session) #47888 (provider auth not attached for v2
POST /api/sessionsessions), Mid-session model resolution silently falls back to global config.model, ignoring the TUI-selected model (selected model receives zero traffic) #47968 (mid-session resolution falls back toconfig.model).
Activity
Jayanth-reflex commentedon Oct 9, 2026
I'd like to take this.
I checked it against current
v2and the cause is still there. When a session has no model and nomodelis configured,Model.default()falls back tovalue.available.find(supportsText)(packages/core/src/model.ts). #53295 added the text-only filter, but nothing checkscapabilities.tools, so a newer model without tools, likeopencode/jev-1.13-free, still becomes the default. The no-model branch ofModelResolver.resolve()has the same gap.My plan is to keep the fix small:
Model.fallback()that picks the newest text model that can call tools. If none of the available models can, it falls back to the newest text model, so setups that only have such models keep working as they do now.Model.default()andModelResolver.resolve()so the two fallbacks stay consistent.packages/core/test/catalog.test.tsfor both cases.The other two points in the issue (reusing the user's recent model for sessions with no model, and showing a clear error when the fallback can't work) look like separate design decisions, so I'll leave them out of this PR unless a maintainer would rather I include them.
I have this working locally and can open the PR against
v2shortly.rekram1-node commentedon Oct 9, 2026
This jev thing should have never occurred in the first place, non llms like jev shoudlnt appear in the mdoels list. we have fixed.