Skip to content

websearch: Firecrawl provider always returns "No search results found" #50217

Open

Description

@jacobspya

Summary

Built-in websearch with provider: firecrawl consistently returns No search results found. Please try a different query. for queries that succeed against the Firecrawl API directly with the same key. Likely response-shape handling bug in the Firecrawl adapter.

Environment

  • opencode version: 2.0.11
  • OS: Darwin 27.2.0 arm64
  • Terminal: Apple_Terminal, xterm-256color
  • Shell: /bin/zsh
  • Install/channel: latest
  • Active plugins: none found in config

Reproduction

  1. Set websearch.provider to firecrawl in opencode.jsonc and provide a valid FIRECRAWL_API_KEY.
  2. Run any current-info prompt, e.g. opencode run --model openai/gpt-5-nano "Search the web and tell me the latest Bun release version number."
  3. Observe Web Search via Firecrawl tool calls completing in <1s with No search results found. Please try a different query.
  4. Control: POST the same query to https://api.firecrawl.dev/v1/search (and /v2/search) with the same key returns HTTP 200 success:true with results.

Expected Behavior

Firecrawl results returned to the model, same as the direct API returns.

Actual Behavior

Every search via the built-in tool returns No search results found. Please try a different query. Tool state shows status: completed, metadata: {provider: firecrawl, truncated: false}. Reproduced 5/5 across 5 distinct queries (latest Bun release x2, Starmind FCC, Starship launch, Tesla Robotaxi).

Additional Context

  • Session export excerpt: {"name":"websearch","metadata":{"provider":"firecrawl","truncated":false},"content":[{"type":"text","text":"No search results found..."}]}
  • No key/config error surfaced; failure is silent empty-result, suggesting the adapter parses a different response shape than the current API returns.
  • Workaround: none via built-in tool; direct Firecrawl API calls work.

Activity

  1. UnixUserDev commented on Oct 18, 2026

    @UnixUserDev

    Confirmed on Linux (CachyOS, x64), opencode 2.0.32, desktop build. Not a flake, and here's the wire evidence for exactly why it comes back empty.

    Root cause: Firecrawl returns an auth failure as a successful MCP result

    POST https://mcp.firecrawl.dev/v2/mcp → HTTP 200, serverInfo: firecrawl-fastmcp 3.30.3. Handshake is fine.

    Then tools/call → firecrawl_search:

    {
      "result": {
        "content": [
          {
            "type": "text",
            "text": "Anonymous keyless access is unavailable for this request.\n\nFix: Create an API key at https://firecrawl.dev/k/... and then:\n- Set the header: Authorization: Bearer YOUR_API_KEY on https://mcp.firecrawl.dev/v2/mcp\nThen start a new session."
          }
        ],
        "structuredContent": {
          "code": "KEYLESS_ACCESS_NOT_AVAILABLE",
          "auth_mode": "keyless",
          "request_id": "...",
          "message": "Anonymous keyless access is unavailable for this request."
        }
      }
    }

    So the provider call succeeds at the protocol level and carries no results / data.web at all. The plugin reads the result array, finds nothing, ?? [], and the tool renders "No search results found. Please try a different query." The real error — a specific, actionable, machine-readable KEYLESS_ACCESS_NOT_AVAILABLE — is discarded.

    Contrast: Parallel works fine keyless

    POST https://search.parallel.ai/mcp → 200, web_search with no API key returns:

    structuredContent: { search_id, results: [10 items], warnings, metadata, session_id }
    

    So keyless hosted access still works for at least one provider. It is specifically Firecrawl that now refuses anonymous search.

    The configured provider is not honoured on the tool path

    This is the part that makes it sticky, and it's why "random"/ANY selection never rotates away:

    • ~/.config/opencode/env.sh has export OPENCODE_WEBSEARCH_PROVIDER=parallel.
    • That variable is present in the environment of the running engine (opencode-cli serve --service, verified via /proc/<pid>/environ).
    • Adding "websearch": { "providerID": "parallel" } to ~/.config/opencode/opencode.jsonc changed nothing either.
    • GET /api/websearch/provider lists exa, firecrawl, parallel, tavily, tinyfish — so the endpoint knows about parallel.
    • Every websearch call, for unrelated queries (tesla roadster, linux kernel 6.18), still returns No search results found.

    Reproduced across three separate queries and two provider configurations. Direct Parallel MCP calls from the same machine return 10/10 results.

    Suggested fixes

    1. Surface provider errors. A structuredContent.code / MCP isError / non-empty content text should become a tool error, not []. fix(core): report MCP web search tool errors instead of empty results #56700 and fix(core): make web search limits non-fatal and report real errors #50938 look like the intended direction here.
    2. Honour websearch.providerID (and OPENCODE_WEBSEARCH_PROVIDER) on the tool execution path, not just in the provider list. Right now the pin appears to be ignored and selection falls back to the first registered provider — firecrawl — which is exactly the broken one.
    3. Rotate on "provider answered with no results and no key", not only on 429.
    4. Docs: the hosted keyless path no longer covers Firecrawl search (auth_mode: keyless is explicitly rejected). Worth stating which providers keyless actually covers, since users reasonably assume "hosted" means it just works.

    Environment

    • opencode 2.0.32 (desktop, opencode-cli serve --service)
    • Linux CachyOS, kernel -cachyos, x64, bash
    • No FIRECRAWL_API_KEY, no EXA_API_KEY, no TAVILY_API_KEY, no PARALLEL_API_KEY configured
    • Config: ~/.config/opencode/opencode.jsonc, shell: bash

    AI-generated with OpenCode — primary model: DeepSeek V4.1 Flash 1017.

  2. opencodejr commented on Oct 28, 2026

    @opencodejr
    Contributor

    Thanks for the detailed report. I can reproduce the silent "No search results found" on 2.0.11, and it still happens on the latest dev (fa1260d). I couldn't test with a valid Firecrawl key, though.

    What I found:

    • The built-in provider doesn't call the REST /v1/search or /v2/search endpoints you tested. It calls Firecrawl's MCP endpoint (https://mcp.firecrawl.dev/v2/mcp, tool firecrawl_search). FIRECRAWL_API_KEY is sent there as a Bearer token.
    • When that endpoint can search, its response is in the layout opencode expects. A keyless search returned 8 results.
    • When the MCP endpoint rejects the request, it still answers HTTP 200 but with an error, e.g. "The Firecrawl API key is invalid or revoked…". opencode drops that error and returns an empty list, so the tool says "No search results found" with no key or config error. That matches what you're seeing.

    So your key is probably getting an error back from the MCP endpoint even though the REST API accepts it, and opencode hides the reason. The script below prints the raw MCP response for a key next to what opencode's provider returns. If you run it with your key and share the raw response (without the key), we'll know the exact cause.

    Related:

    Reproduction script (firecrawl-repro.sh)
    #!/usr/bin/env bash
    # Reproduces #50217: built-in Firecrawl websearch returns no results (-> "No search results found")
    # whenever Firecrawl's MCP endpoint answers with an MCP tool error, and the error text is dropped.
    # Usage: run from an opencode checkout (dev or a v2.x tag) after `bun install`.
    #   FIRECRAWL_API_KEY=<key> ./firecrawl-repro.sh
    set -euo pipefail
    REPO=${REPO:-/workspace/repo}
    echo "== Raw Firecrawl MCP response for this key (what the adapter receives):"
    curl -s -m 20 -X POST https://mcp.firecrawl.dev/v2/mcp \
      -H 'content-type: application/json' -H 'accept: application/json, text/event-stream' \
      ${FIRECRAWL_API_KEY:+-H "Authorization: Bearer $FIRECRAWL_API_KEY"} \
      -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"firecrawl_search","arguments":{"query":"latest Bun release version","limit":8}}}' | head -c 400; echo
    mkdir -p "$REPO/packages/core/scratch"
    HTTP_MOD="effect/http"; grep -q 'effect/unstable/http' "$REPO/packages/core/src/plugin/websearch/firecrawl.ts" && HTTP_MOD="effect/unstable/http"
    cat > "$REPO/packages/core/scratch/live.ts" <<TS
    import { Effect, Layer } from "effect"
    import { FetchHttpClient } from "$HTTP_MOD"
    import { AppNodeBuilder } from "@opencode/core/effect/app-node-builder"
    import { LayerNode } from "@opencode/util/effect/layer-node"
    import { Credential } from "@opencode/core/credential"
    import { Config } from "@opencode/core/config"
    import { Bus } from "@opencode/core/bus"
    import { Form } from "@opencode/core/form"
    import { Integration } from "@opencode/core/integration"
    import { WebSearch } from "@opencode/core/websearch"
    import { WebSearchFirecrawl } from "@opencode/core/plugin/websearch/firecrawl"
    import { host, integrationHost, webSearchHost } from "../test/plugin/host"
    const layer = Layer.merge(
      AppNodeBuilder.build(LayerNode.group([Integration.node, Credential.node, Bus.node, Form.node, WebSearch.node]), [
        Config.node.replace(Config.testLayer()),
      ]),
      FetchHttpClient.layer,
    )
    const program = Effect.gen(function* () {
      const integrations = yield* Integration.Service
      const websearch = yield* WebSearch.Service
      yield* WebSearchFirecrawl.Plugin.effect(host({ integration: integrationHost(integrations), websearch: webSearchHost(websearch) }))
      yield* websearch.select("firecrawl")
      const r = yield* websearch.query({ query: "latest Bun release version" })
      console.log("== opencode Firecrawl provider: key", process.env.FIRECRAWL_API_KEY ? "set" : "unset", "-> results:", r.results.length)
    })
    await Effect.runPromise(program.pipe(Effect.scoped, Effect.provide(layer)) as any)
    TS
    cd "$REPO/packages/core" && bun scratch/live.ts 2>&1 | grep "^=="

    Run it: bash firecrawl-repro.sh

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions