Skip to content

searxng web search backend always reports 'unavailable' even when SearXNG responds 200 (instanceof Response bug) #8

Description

@mihailmariusiondev

Version: betahi-copilot-bridge 0.20.16

Setup:

  • ~/.claude/settings.json: {"env": {"COPILOT_WEB_SEARCH_BACKEND": "searxng"}}
  • SearXNG running locally, confirmed reachable and returning valid JSON:
    curl -s -m 5 -o /dev/null -w '%{http_code}' http://localhost:8080   # -> 200
    curl -s 'http://localhost:8080/search?q=test&format=json'           # -> real JSON results
    

Repro:

  1. Start the bridge with the above config, hit /v1/messages with a web_search_20250305 tool and a query.
  2. The bridge always returns web_search_tool_result_error with error_code: unavailable, even though SearXNG is up and answering.
  3. If you let the model paraphrase the tool result, it leaks the internal diagnostic text, which reveals the real cause:
    Details:
    - Local SearXNG is not running at `http://localhost:8080`
    - Error: `[object Response]`
    

Root cause (from reading dist/main.js):
checkSearxngAvailable() does:

const response = await fetch(DEFAULT_SEARXNG_BASE_URL, {...}).catch((error) => error);
if (response instanceof Response) return; // success path
const detail = response instanceof Error ? response.message : String(response);
return ["Local SearXNG web search is not available.", detail ? `Error: ${detail}` : ""].filter(Boolean).join("\n");

The detail value stringifies to literally "[object Response]", which only happens when response is a real Response object but response instanceof Response evaluates to false. That's a classic dual-realm / duplicate-module symptom: the bundle likely has two different Response class references in scope (e.g. one from Node's built-in global fetch/undici, another re-exported through the bundled undici import used elsewhere in the file), so the identity check fails even though the object genuinely is a successful fetch response.

I instrumented globalThis.fetch in a wrapper before importing dist/main.js and confirmed the readiness fetch to http://localhost:8080 really does return HTTP 200 — the bridge just never recognizes it as such.

Impact: the local searxng backend is unusable as documented — it always falls through to the failure branch regardless of whether SearXNG is actually reachable. (For contrast, COPILOT_WEB_SEARCH_BACKEND=copilot-cli works fine for me, since checkCopilotCliAvailable() doesn't use this instanceof pattern — it shells out to copilot --version and checks stdout instead.)

Suggested fix: replace response instanceof Response with a duck-typed check (e.g. typeof response?.ok === "boolean" or checking for response?.status), since instanceof against a bundled/global class reference is fragile in this kind of setup.

Happy to test a fix if you push one. Thanks for the great tool!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions