Repository navigation
Description
Description
A plugin using ctx.model.transform + editor.remove() to restrict a provider's models has no effect in 2.2.5: removed models still appear in GET /api/model and can still be selected and called via --model. There are no errors — the transform fires and the editor reflects the removals, but nothing propagates to the catalog or to model resolution.
This is the exact pattern recommended as the interim workaround for #49986 (comment) and mentioned again in #61601, so anyone following it gets silently non-functional filtering.
Expected: removing a model via the transform hides it from the catalog and makes explicit selection fail with Model unavailable, consistent with providers.<id>.models.<model>.disabled: true.
Plugins
Only the reproducing plugin itself (see steps). No other plugins.
OpenCode version
2.2.5 (macOS, arm64, shared background service; installed via homebrew)
Steps to reproduce
Prerequisite: any provider with ≥2 models (a custom openai-compatible provider in the user config works; no credentials needed for the catalog part).
-
In a scratch directory, save
.opencode/plugins/model-filter.ts:import { Plugin } from "@opencode/plugin" const PROVIDER = "<provider-id>" // e.g. a custom provider const KEEP = "<one-model-id-on-it>" export default Plugin.define({ id: "model-filter", async setup(ctx) { await ctx.model.transform((editor) => { for (const model of editor.list(PROVIDER)) { if (model.id !== KEEP) editor.remove(PROVIDER, model.id) } }) }, })
-
Wait ~2s for the hot reload, then from that directory:
opencode api get "/api/model?location%5Bdirectory%5D=$(pwd)" # → all of the provider's models are still listed opencode run --model <PROVIDER>/<a-removed-model> "Reply with exactly: OK" # → still succeeds
Evidence that the transform does run
Instrumenting the plugin with a file probe (plugin console.warn output does not appear in the server log):
12:09:59.189 transform registered
12:09:59.194 transform fired, removed 0, editor now lists 0 ← first pass, state still empty
12:09:59.217 transform fired, removed 16, editor now lists 3 ← second pass: removals applied
After the second firing the editor lists 3 models, yet GET /api/model for the same location returns 19, and explicit --model selection of a removed model succeeds. ctx.location inside the plugin matches the queried directory, so this does not look like a location mismatch.
Notes
- The two firings suggest the state rebuild pipeline re-runs transforms as documented, and the editor's own state converges correctly — the removals just never reach the registry that
GET /api/modeland model resolution read. From a quick source pass,ctx.modelis built fromhost.model(packages/plugin/src/promise/adapter.ts), andState(packages/core/src/state.ts) re-runs registered transforms on rebuild, which matches the observed firings. I could not pin down where the two views diverge. - The community plugin GaboEI/opencode-model-filters-v2 is built on the same hook and reports it working on 2.0.x, so this may be a regression in the 2.0 → 2.2.5 window.
References
- [FEATURE]: Add a native V2 replacement for provider model filtering #49986 — native replacement for V1 whitelist/blacklist; the bot's comment recommends this plugin pattern
- [FEATURE]: Model-level policies for per-model governance (model.use, or provider.use with provider/model resources) #61601 — model-level policies request; same recommendation
- provider.<id> whitelist/blacklist has no effect on the model picker (confirmed for both built-in opencode and openrouter providers) #61110 — user report that legacy
whitelisthas no effect
Activity
opencodejr commentedon Nov 10, 2026
Thanks for the detailed report and the probe output, they made this quick to track down.
I tested on 2.2.5 with a plugin like yours and found that
editor.remove()works, but only for models that are not listed in your config:opencode): removing every model except one worked, andGET /api/modelreturned only the model I kept.providers.<id>.modelsin config: the transform fired and the editor showed only the kept model, as in your probe./api/modelstill listed all of them.The second case is how config precedence is designed to work. OpenCode applies config to models in a model transform that is registered after plugin transforms, so config wins. That step can bring back or re-enable any model listed in config. Because your custom provider lists its models in config, that step creates them again after your plugin removes them. The same code has been there since 2.0.x (I checked 2.0.9, 2.0.26 and 2.1.0), so I don't think this is a regression in the 2.0 → 2.2.5 window.
Ways to hide those models now:
/api/modelin my test.providers.<id>.models.<model>.disabled: truein config, or stop listing those models there.You're right that the
remove()suggestion on #49986 and #61601 doesn't mention this limitation. If you'd likeremove()from a plugin to win over models listed in config, that would be a change to the designed precedence, so it may be better filed as a feature request. Related: #54480 (external transforms run before the config transform), #49986, #61601.opencodejr commentedon Nov 13, 2026
I'm closing this for now because there haven't been any replies since my last comment. On 2.2.5,
editor.remove()worked for catalog models. Models listed underproviders.<id>.modelsin config came back because the config model transform runs after plugin transforms, so config takes precedence. To hide those models now, setenabled = falsefrom the plugin, or setdisabled: truein config. If you'd like a plugin'sremove()to take precedence over config, a feature request would be the right place for that. #54480, #49986 and #61601 are related.If you still see this with
enabled = false, or with models that aren't listed in your config, reply here and the issue will reopen.