Repository navigation
Description
What occurs
The host drops a provider entry in opencode.json when the entry does not agree with the configuration schema. The host writes one warning to the service log. The host then continues. The configured model names a provider that the catalog does not contain.
Model selection then uses the first model of the catalog that is available. That model is a free model of the built-in OpenCode provider. The prompt and all tool calls go to that provider. The client does not report that the host dropped the configured provider.
An explicit model selection on the command line stops with an error for the same configuration. Only the configured model falls back to a different provider.
Steps to reproduce
The reproduction is possible without network access. Run the host in a network namespace so that no request goes out of the namespace.
unshare -rn
ip link set lo up
Use isolated configuration, data, state, and cache directories so that the active user configuration is not read.
export XDG_CONFIG_HOME="$SCRATCH/config"
export XDG_DATA_HOME="$SCRATCH/data"
export XDG_STATE_HOME="$SCRATCH/state"
export XDG_CACHE_HOME="$SCRATCH/cache"
export HOME="$SCRATCH/home"
Write $XDG_CONFIG_HOME/opencode/opencode.json:
{
"update": "disable",
"model": "probe/probe-1",
"providers": {
"probe": {
"name": "Probe",
"package": "aisdk:@ai-sdk/openai-compatible",
"settings": {
"apiKey": "test-key",
"baseURL": "http://127.0.0.1:9/v1"
},
"models": {
"probe-1": {
"name": "Probe 1",
"capabilities": {
"tools": true,
"reasoning": true,
"input": ["text"],
"output": ["text"]
}
}
}
}
}
}The field capabilities.reasoning is invalid. The V2 schema accepts only an object, for example {"supported": true}.
Run one prompt:
opencode run --standalone "hi"
The service log contains one warning:
level=WARN message="configuration normalization diagnostic" path=$.providers.probe kind=invalid action="skipped malformed recognized value"
The client output does not name the provider probe:
> build · big-pickle
The session database records the provider and the model of the assistant message as opencode and big-pickle.
The host then sends a request to the endpoint of the built-in provider. The request does not succeed in the namespace. The run ends with an error that names no provider:
Error: ECONNREFUSED: Unable to connect. Is the computer able to access the url?
With network access, the same request succeeds and the model answers.
Correct result
The run stops with an error that the client shows. The error names the provider probe. The host does not use a provider that the user did not select.
A warning alone is sufficient only if the client shows the warning. The client must not start a model request after the host drops a provider entry.
Actual
The host drops the invalid provider entry. The host continues with a different provider. The prompt and the tool calls go to the built-in OpenCode provider. The client shows the model name in the run header. The client does not report that the host rejected the configured provider. Only the service log contains the configuration warning.
The same configuration with an explicit model selection stops with an error:
opencode run --standalone --model probe/probe-1 "hi"
Error: Model unavailable: probe/probe-1
Why this is important
A configuration mistake sends the prompt and the tool access to a provider that the user did not select. A user who configures a private, local, or self-hosted provider can send source code, secrets in the prompt, and tool results to a hosted provider. The user receives no notice of that change.
The failure is also hard to find. The client output names the model that ran. The client output does not state that the host dropped the configured provider. The user must read the service log to find the cause.
Workarounds
Two settings stop the fallback. The two runs below use the configuration above.
The setting disabled_providers removes the built-in provider from the catalog. The run then stops with an error.
{
"disabled_providers": ["opencode"]
}Error: No model is available for session <session id>
The environment setting OPENCODE_DISABLE_FREE_MODELS=1 removes the free models of the built-in provider. The run then stops with the same error.
Source locations
The line numbers refer to commit 17b79f4e0c16dcff200b0417567ed22aeb2d7166 on the dev branch.
packages/core/src/config/normalize.ts:169reads theprovidersmap and decodes each entry.packages/core/src/config/normalize.ts:699-717drops an entry when the decode does not succeed. The other entries continue.packages/core/src/config/normalize.ts:842-844records the diagnostic with the typeinvalidand the messageskipped malformed recognized value.packages/core/src/config.ts:122-130logs each diagnostic as a warning. The other configuration values continue.packages/schema/src/config/provider.ts:73-78specifies theReasoningfield as an object.packages/schema/src/config/provider.ts:98-104adds that field to the model configuration.packages/core/src/config/plugin/provider.ts:108reads the configured model.packages/core/src/config/plugin/provider.ts:115-116stores it as the default model. The code does not check that the catalog contains the provider.packages/core/src/model.ts:271-276selects the requested model only when the catalog contains the model and the host enables the model. In the other condition the method selects the first available model that accepts text.packages/core/src/model.ts:296-312puts the free modelbig-picklefirst in the list of free models. As a result the first available model isopencode/big-pickle.packages/core/src/plugin/provider/opencode.ts:368-372enables the built-in provider without a credential. The code sets the API key topublicwhen the host has no key and no connection.packages/core/src/session/runner/model.ts:77-89selects the model of the agent when the catalog contains the model. As a result the model resolver runs in the other condition.packages/core/src/model-resolver.ts:416-431calls the default model. The code then calls the first available model.
Version
The reproduction uses v2.1.4. The same code is in dev at the commit above. At 32b57015e5b491a789fba1534ecb472e676c9d8e these files have only import changes.
Activity
opencode-agent commentedon Oct 25, 2026
Thanks for the very precise report, @iceteaSA. I reproduced this on v2.1.4, and it still happens on the latest
dev(32b5701).With your config (top-level
model: probe/probe-1, pluscapabilities.reasoning: truein theprobemodel):opencode run --standalone "hi"writes theconfiguration normalization diagnostic ... path=$.providers.probe kind=invalidwarning to the service log only. The run header shows> build · big-pickle, and the session stores the assistant message asopencode/big-pickle. In my sandbox, with network access, the hosted model actually answered. Ondev, the request still went to the OpenCode endpoint and was refused only because of the free-usage limit.opencode run --standalone --model probe/probe-1 "hi"fails withError: Model unavailable: probe/probe-1, as you described.So a configured default model whose provider was dropped falls back to the first available model, which is the built-in OpenCode provider, and the client shows nothing about it. The script below reproduces it.
Related issues: #50340 and #53744 (one invalid field drops the whole provider, with only a log warning), #54786 (rejected config files are ignored without a visible error) and #55150 (work unexpectedly going to a built-in free model).
Until this is addressed,
"disabled_providers": ["opencode"]orOPENCODE_DISABLE_FREE_MODELS=1prevents the fallback, as you noted.Reproduction script (repro.sh)
Run it:
bash repro.sh