Repository navigation
Description
Summary
In V1, websearch worked with no API key or search account whenever the OpenCode or OpenCode Go provider was in use (documented V1 behavior). In V2 the keyless capability still exists — Exa, Parallel, and Firecrawl run without any key/account — but it is undocumented: the V2 websearch docs present all five providers as requiring a /connect account or an environment key, or the paid Console hosted search ($0.01/search). The V1→V2 migration guide lists no websearch change, while stating that any other broken V1 behavior should be treated as a compatibility bug. On top of that, websearch.provider: "random" can stick to a provider that returns no results or fails (fallback is documented for HTTP 429 only), so search can silently degrade after upgrading.
Environment
- opencode version: 2.0.20
- OS: Darwin 25.2.0 (macOS arm64)
- Terminal: xterm-256color (
$TERM_PROGRAMunset) - Shell: /bin/zsh
- Install/channel: latest (curl install,
~/.opencode/bin/opencode) - Active plugins: none in user config (reproduced with
--standalone)
Reproduction
- With no search-provider account or key connected, set
websearch.providertoexaand run a search. It succeeds via Exa's keyless MCP endpoint (this is undocumented behavior). - With no search-provider account or key connected, set
websearch.providertorandomand run a search. Observed: the search executes via Firecrawl and returnsNo search results found, with no fallback to a working provider, and the session keeps the selected provider. - (Contrast, V1) The V1 docs state the tool was available with no key whenever the OpenCode / OpenCode Go provider was used.
Commands used (v2.0.20, standalone server, no search accounts connected):
OPENCODE_CONFIG_CONTENT='{"websearch":{"provider":"exa"}}' opencode run --standalone --auto --model <model> "call websearch ..."
OPENCODE_CONFIG_CONTENT='{"websearch":{"provider":"random"}}' opencode run --standalone --auto --model <model> "call websearch ..."Expected Behavior
- Either restore V1's automatic keyless behavior for the OpenCode / OpenCode Go provider, or document the new selection model — including that Exa, Parallel, and Firecrawl work without any key or account — and list the change in the migration guide.
randomshould recover when the chosen provider returns no results or fails, not only on HTTP 429.
Actual Behavior
- V2 websearch docs (
/v2/docs/websearch) say "Connect an account from the TUI with/connect, or set the provider's environment variable" for all five providers, and never mention keyless use. Console hosted search costs $0.01/search. - The V2 migration guide (
/v2/docs/migrate-v1) lists only three intentional breaking changes (plugins, server API, terminal client config) and says: "If supported behavior described in this guide stops working in V2, treat it as a compatibility bug rather than an expected migration requirement." - With
random, a provider that returns no results (or fails for anything other than HTTP 429) is not retried; sessions stick to their selected provider.
Additional Context
- V1 docs (still live): https://opencode.ai/docs/tools/ — "This tool is only available when using the OpenCode or OpenCode Go provider... No API key is required — the tool connects directly to the backend's hosted MCP service without authentication." V1 also documented
OPENCODE_ENABLE_EXA/OPENCODE_ENABLE_PARALLEL; the V2 docs don't mention them (and they don't appear in the v2.0.20 binary strings). - V1 changelog v1.18.19 (2026-08-20): "Enabled web search when using the OpenCode Go provider."
- V2 docs: https://opencode.ai/v2/docs/websearch · Console hosted: https://opencode.ai/v2/docs/console/websearch
- User impact: keyless search worked for months on V1 (63 recorded
websearchtool calls between 2026-05-17 and the V2 migration on 2026-09-24); after upgrading to V2, search degraded/failed until the provider was pinned totavilymanually. The provider-selection change is not explained anywhere: no changelog entry, no migration note.
Activity
opencode-agent commentedon Oct 10, 2026
Thanks for the detailed report. I can confirm the
randompart, and it still happens on the latest v2 (4617210).With
websearch.provider: "random", opencode only switches to another provider after an HTTP 429. When the provider it picked returns no results, the search ends with "No search results found", the other providers are never tried, and later searches in that session go to the same provider. A non-429 error (an HTTP 500, for example) fails the search the same way, with no fallback. The test below shows both cases with two fake providers. The retry code is the same in 2.0.20 and the current v2 branch.I couldn't check the live provider behavior here: in this sandbox every provider failed inside opencode, including Exa, even though Exa's endpoint answered a direct request. So I didn't confirm the keyless Exa success or Firecrawl's empty results. The Firecrawl empty results look like #50217. I also didn't test the documentation and migration-guide gaps, so those are for maintainers.
Until this is addressed, pinning a provider that works for you (as you did with
tavily) avoids the problem.Related:
randomReproduction script (websearch-random-repro.test.ts)
Run it:
bun websearch-random-repro.test.tscarson2222 commentedon Oct 12, 2026
Checked live on v2.0.27 with no search keys connected. The Firecrawl empty results come from Firecrawl's keyless quota, not from a response-shape bug.
A direct
firecrawl_searchcall tohttps://mcp.firecrawl.dev/v2/mcpreturns HTTP 200 with this tool result:{ "isError": true, "structuredContent": { "code": "KEYLESS_QUOTA_EXHAUSTED", "auth_mode": "keyless" }, "content": [{ "type": "text", "text": "You've hit Firecrawl's free MCP rate limit. To continue using without limits, create a Firecrawl API key. ..." }] }WebSearchMcp.call(packages/core/src/plugin/websearch/mcp.ts) ignoresisError. The Firecrawl adapter can't decode the text as search JSON, so it returns[]and the model sees "No search results found". The status is 200, sorandomnever cools the provider down or tries another one.Exa's keyless endpoint behaves the same way once its limit is hit. During a benchmark run I got some HTTP 429s and some HTTP 200 responses whose tool text was "You've hit Exa's free MCP rate limit...".
parseResultsfinds noURL:lines in that text and returns[].In my session history, 10 of 39 Firecrawl searches under
randomended this way.Suggested fix: when a tool result has
isError: true, fail with the provider's message. Treat quota codes likeKEYLESS_QUOTA_EXHAUSTEDas a rate limit sorandommoves on to the next provider.fihaaade commentedon Oct 18, 2026
I'll take the empty-results part. Following @carson2222's trace, the plan is to make
WebSearchMcp.callfail when the MCP tool result hasisError: true, with the provider's message, so the quota error shows up instead of "No search results found". This applies to all MCP-based providers. Whetherrandomshould also rotate on that error, and not only on HTTP 429, changes retry behavior, so I'll leave it out of the PR and mention it there.u-k-g commentedon Oct 26, 2026
+1
Opencode v2.1.4 was hitting 'No search results found. Please try a different query.' on every websearch. Explicitly pinning websearch provider to Exa fixed it:
fix(core): report MCP web search tool errors instead of empty results