siyuan-note/siyuan · error

web search read response failed: %s

Error message

web search read response failed: %s

What it means

Returned at websearch.go:89 when io.ReadAll(resp.Body) fails after the POST to Exa connected and headers came back. Unlike WebFetch, this reader has NO size limit applied — the entire response body is read into memory. The %s is the stream read error.

Source

Thrown at kernel/util/websearch.go:89

			Name: "web_search_exa",
			Arguments: map[string]any{
				"query":      query,
				"type":       "auto",
				"numResults": 8,
				"livecrawl":  "fallback",
			},
		},
	}

	resp, err := httpclient.NewBrowserRequest().SetHeader("Accept", "application/json, text/event-stream").SetBody(reqBody).Post(exaURL)
	if err != nil {
		return "", errors.New("web search failed: " + err.Error())
	}
	defer resp.Body.Close()

	bodyBytes, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", errors.New("web search read response failed: " + err.Error())
	}
	body := string(bodyBytes)

	preview := body
	if len(preview) > 500 {
		preview = body[:500]
	}
	logging.LogInfof("websearch response: status=%d, len=%d, preview=%s", resp.StatusCode, len(body), preview)

	text := parseMcpResponse(body)
	if text == "" {
		return "No search results found. Please try a different query.", nil
	}

	return truncateRunes(text, maxWebSearchChars), nil
}

func parseMcpResponse(body string) string {

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Retry the search — stream drops are often transient.
  2. Stabilise the network path (disable flaky VPN, switch off aggressive proxy timeouts).
  3. Be aware there is no body-size cap; on pathological responses the process can grow large before failing.
  4. If repeatable, capture resp.StatusCode (logged at websearch.go:97) to distinguish a server-side truncation.
Defensive patterns

Strategy: retry

Type guard

func isWebSearchReadFailed(err error) bool {
    return err != nil && strings.HasPrefix(err.Error(), "web search read response failed:")
}

Try / catch

out, err := util.WebSearch(q, key)
if err != nil && isWebSearchReadFailed(err) {
    // mid-stream drop, often transient
    out, err = util.WebSearch(q, key)
}

Prevention

When it happens

Trigger: Connection reset mid-stream, read timeout while Exa's livecrawl is still producing output, server closing the SSE/streaming channel early, or memory pressure on an unusually large response.

Common situations: Slow/unstable network during a long livecrawl, intermediary dropping long-lived responses, Exa returning an unexpectedly large payload, client read deadline too tight.

Related errors


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/13885c22782996ca. Report an issue: GitHub.