anomalyco / anomalyco/opencode

server: sessions without an explicit model ignore the user's model

Open
#50,032 0 comments 0 reactions 0 assignees View on GitHub

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

  1. Have the server running (opencode serve or the shared background service).
  2. Create a session with no model in the body: POST /api/session with {}.
  3. Read what such a session resolves to: GET /api/model/default. It returns the same value for every project directory.
  4. Send a prompt: POST /api/session/{id}/prompt with {"text":"reply with the single word OK"}.
  5. 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-free was released 2026-09-15; before that the newest entry was opencode-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/session accepts model) or afterwards (POST /api/session/{id}/model), or set a root model in 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": true does 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/session sessions), #47968 (mid-session resolution falls back to config.model).

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.