Repository navigation
Description
Summary
The provider.models plugin hook is never invoked for providers that are declared by a
plugin's own config hook (i.e. providers that exist only in config, not in the models.dev
catalog). The hook loop runs against database before config providers are merged into it,
so database[providerID] is still undefined and the provider is skipped.
Built-in plugins (modal, github-copilot, openai/codex, digitalocean) are unaffected
because their providers already exist in the models.dev catalog.
Where
packages/opencode/src/provider/provider.ts (observed on v1.18.18):
const database = mapValues(catalog, toPublicInfo) // :1376 models.dev catalog only
const configProviders = Object.entries(cfg.provider ?? {}) // :1414 read, not merged yet
for (const hook of plugins) { // :1424
const p = hook.provider
const models = p?.models
if (!p || !models) continue
const providerID = ProviderV2.ID.make(p.id)
if (disabled.has(providerID)) continue
const provider = database[providerID]
if (!provider) continue // :1433 <-- config providers exit here
...
}
// extend database from config // :1451 <-- too late
for (const [providerID, provider] of configProviders) { ... }Reproduce
A plugin that declares a provider in its config hook and also exports
provider: { id, models }:
export default async (ctx, options) => ({
provider: {
id: "my-provider",
models: async () => { console.log("NEVER CALLED"); return {} },
},
config: async (cfg) => {
cfg.provider = { ...(cfg.provider ?? {}), "my-provider": { /* ... */ } }
},
})Expected: models() is called and its catalogue is used.
Actual: models() is never called. The provider still appears in /config/providers, but
only with the snapshot published by the config hook.
Impact
For an OpenAI-compatible gateway (our case is LiteLLM), the config hook is a boot-time
snapshot: the catalogue is captured once when the process starts. provider.models is the
documented way to resolve a provider's catalogue on demand, so a gateway plugin cannot
refresh its model list without restarting the whole server — which, for opencode serve,
means dropping every in-flight session. We ended up with 16 models in the picker that the
gateway had stopped serving; every request against them returned HTTP 400 until a restart.
Suggested fix
Move the plugin hook loop after the "extend database from config" loop, or seed database
with the config providers before running the hooks. Happy to send a PR if you prefer a
particular ordering.
Note on hot reload
Separately, the provider list is memoised in InstanceState.make and there is no call to
InstanceState.invalidate for it, so even with the ordering fixed the hook would run once
per instance rather than on demand. Filing that here only as context — the ordering bug is
the concrete defect.
Version
v1.18.18
Activity
github-actions commentedon Aug 22, 2026
This issue might be a duplicate of existing issues. Please check:
provider.models()hook no longer populates custom providers — same root cause (database[providerID]lookup skips non-catalog providers before config merge)database[providerID]undefined short-circuit for third-party/custom providersthdxr commentedon Oct 11, 2026
Thanks for the report! OpenCode 2.0 is now out and development is focused on V2, so we're closing issues filed against OpenCode 1. Please upgrade (
opencode upgrade --major, details: https://anoma.ly/notes/opencode-2-0), and if this is still relevant on 2.0, reopen this issue or open a new one with your 2.0 version.— from 𝕺𝖕𝖊𝖓𝕮𝖔𝖉𝖊