anomalyco / anomalyco/opencode
server: sessions without an explicit model ignore the user's model
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 209k
- Forks
- 27.5k
- PR merge metrics
- PR metrics pending
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: #43055 (config default model ignored, falls back to first provider's model), #39125 (cold-start default model is stale), #47888 (provider auth not attached for v2
POST /api/sessionsessions), #47968 (mid-session resolution falls back toconfig.model).
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by tracing POST /api/session when the model is omitted, then follow GET /api/model/default and the first POST /api/session/{id}/prompt resolution path. Compare the selected model with the user's usable sessions and configured default. Done means an omitted model selects a callable user-level or configured model, or returns a clear actionable resolution error instead of silently using an unusable catalog entry.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100