Skip to content

fix(core): do not run on another provider when the configured one is missing - #59238

Closed
2SinceJuly wants to merge 4 commits into
anomalyco:devfrom
2SinceJuly:fix/missing-provider-fallback
Closed

2SinceJuly wants to merge 4 commits into
anomalyco:devfrom
2SinceJuly:fix/missing-provider-fallback

Conversation

@2SinceJuly

@2SinceJuly 2SinceJuly commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Issue for this PR

Closes #58909

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

When model names a provider whose config entry the schema rejected, the provider is dropped from the catalog. Model.default then returned the first available model, so the configured default silently resolved to a different provider — in practice the built-in free one — and the prompt and its tool calls ran there.

The default now resolves to nothing when its provider was never registered, and ModelResolver no longer picks an arbitrary model as a second fallback. A configured model on a provider that still exists keeps the existing fallback, including a disabled provider or a model that later left the catalog.

The run now stops with No model is available for session … instead of using another provider. It does not yet name the dropped provider; making the rejected config entry itself fail would be a larger change.

How did you verify your code works?

With the config from the issue (invalid capabilities.reasoning, model: probe/probe-1) on an isolated XDG_* setup:

  • Before: > build · big-pickle, then the free-usage error — the prompt went to the built-in provider.
  • After: Error: No model is available for session …, and no request leaves for another provider.

Tests:

  • Two regression cases: Model.default and ModelResolver.resolve return nothing when the configured provider is missing. Both fail on dev and pass with the change.
  • bun test for the catalog, model-free, model-resolver, session-runner, session-runner-recorded, session-title and generate suites in packages/core: 349 pass.
  • The same seven suites in a Linux container (oven/bun:1.4.4, arm64): 349 pass.
  • bun typecheck in packages/core reports no type errors.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

Added by the opencode bot (AI-generated).

Related: #55150, #50340, #53744, #54786

…missing

A `model` that names a provider whose config entry the schema rejected made
`Model.default` fall back to the first available model, so prompts and tool
calls silently ran on the built-in free provider.

Resolve nothing in that case, and stop the resolver from picking an arbitrary
second fallback. A configured model on a provider that still exists keeps its
existing fallback.

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix works: with the config from #58909, the run on the base goes to opencode/big-pickle and sends the request to the OpenCode endpoint. With this PR it stops with No model is available for session … and nothing goes to another provider. The explicit --model path is unchanged. I checked it with the issue's reproduction script, run from source on both checkouts. The catalog, model-free, model-resolver and session-runner tests in packages/core pass (317), and typecheck:tsc passes.

Public API: unchanged. The default-model HTTP route and the plugin models.default() can now return nothing when the configured provider is missing.

Concerns:

  1. The resolver change removes the package fallback for users with no model configured too, not just for a configured default (see the inline comment).
  2. The new error doesn't say which model or provider could not be found, unlike Model unavailable: probe/probe-1 on the --model path. Naming probe/probe-1 would make the dropped config entry much easier to find. This can be a follow-up.
  3. Side effect worth confirming: a default on a provider removed by managed policy (provider.use deny) also gives "no model" now, instead of falling back to an allowed provider. This is probably the right behaviour, but please confirm it is intended.

Related: #55150, #50340, #53744, #54786

Comment thread packages/core/src/model-resolver.ts Resolved
Only a configured default whose provider was dropped should stop resolving.
When no model is configured, the resolver still prefers the first text model
that has a package, as before.
@2SinceJuly

Copy link
Copy Markdown
Contributor Author

Thanks — addressed the three points.

  1. Fixed in 7e88b3f: the available().find(hasPackage && supportsText) fallback is back for every case where Model.default() returns a model without a package, including when no model is configured. Only the case where the default resolves to nothing (dropped provider) stops resolving now.

  2. Agreed, naming the configured ref would be better. The run currently stops with No model is available for session …; surfacing the configured provider/model there needs the runner to read the configured default, so I left it as a follow-up.

  3. Intended: a provider.use deny removes the configured provider, and running on an allowed provider instead would repeat the "ran somewhere I did not choose" problem this issue is about. Happy to distinguish policy-denied providers if you would rather keep the fallback for that case.

Re-ran the catalog, model-free, model-resolver, session-runner, session-runner-recorded, session-title and generate suites in packages/core: 349 pass on macOS and in a Linux container (oven/bun:1.4.4, arm64), and tsc -b is clean.

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new commit addresses the earlier point: with no model configured, the resolver again falls back to the first text model that has a package. The issue's reproduction still stops with No model is available for session … instead of running on big-pickle. The catalog, model-free, model-resolver and session-runner tests pass, and so does typecheck:tsc. Public API: unchanged.

One remaining case, in the restored fallback: it also runs when a model is configured but has no package (see the inline comment).

Comment thread packages/core/src/model-resolver.ts Resolved
Only an unconfigured default may pick another loadable model. A configured
default without a package now fails while loading, like `--model` does, instead
of running on a different provider.
@2SinceJuly

Copy link
Copy Markdown
Contributor Author

Fixed in d619287. Model.default now reports the configured ref through an internal configuredDefault() accessor, and the resolver only falls back to another loadable model when no model is configured. A configured default without a package is used as it is, so loading it fails with Unsupported package — the same result the --model probe/probe-1 path gives.

Regression test: with a registered probe provider and a package-less probe/probe-1 as the configured default, resolve() now fails with UnsupportedPackageError instead of resolving opencode's model. It fails on the previous commit and passes now.

The plugin-facing API is unchanged; this only adds one method to the internal Core Model service.

Re-ran the catalog, model-free, model-resolver, session-runner, session-runner-recorded, session-title and generate suites: 350 pass on macOS and in the Linux container, tsc -b clean.

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both earlier points are addressed. With no model configured, the resolver again falls back to a text model that has a package. A configured default is now used as it is: a model with no package fails with Unsupported package for probe/probe-1, the same as --model, instead of running on another provider. I checked both cases from source, along with the #58909 reproduction, which still stops with No model is available for session …. The nearby core tests pass (358) and so does typecheck:tsc. Public API: unchanged (Model.configuredDefault is a new method on the internal Model.Service only).

@2SinceJuly
2SinceJuly force-pushed the fix/missing-provider-fallback branch from 189b5d4 to a7d6276 Compare October 30, 2026 04:40
@2SinceJuly

Copy link
Copy Markdown
Contributor Author

@rekram1-node bumping this one since it's been quiet since the 26th.

Small change, 4 files. I just checked it against current dev and it merges clean, no conflicts. There's a regression test that fails on the previous head and passes on this one.

The bug itself: when a provider entry fails schema validation, Model.default falls back to the first available model, which in practice is the built-in free one. So the prompt and its tool calls end up going to a provider the user never selected. Not great if you configured a provider and just got one field wrong.

Let me know if you'd rather see it scoped differently or split up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

config: the host drops an invalid provider entry and falls back to a built-in provider

1 participant