siyuan-note/siyuan · error
fetch failed: %s
Error message
fetch failed: %s
What it means
Returned at webfetch.go:56 when httpclient.NewBrowserRequest().Get(rawURL) fails at the transport layer. By this point the URL has already passed scheme (http/https), host-presence, and SSRF (CheckHostSSRF) checks, so the failure is purely network-level: DNS, TCP, TLS, proxy, or timeout. The wrapped %s is the underlying httpclient/go error string.
Source
Thrown at kernel/util/webfetch.go:56
maxWebFetchChars = 50000
)
func WebFetch(rawURL, format string) (string, error) {
u, err := url.Parse(rawURL)
if err != nil || (u.Scheme != "http" && u.Scheme != "https") {
return "", errors.New("URL must start with http:// or https://")
}
if u.Host == "" {
return "", errors.New("URL has no host")
}
if err := CheckHostSSRF(u.Hostname()); err != nil {
return "", err
}
resp, err := httpclient.NewBrowserRequest().Get(rawURL)
if err != nil {
return "", errors.New("fetch failed: " + err.Error())
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
return "", fmt.Errorf("HTTP %d", resp.StatusCode)
}
contentType := resp.Header.Get("Content-Type")
maxReadBytes := int64(maxWebFetchBytes)
if !strings.HasPrefix(contentType, "text/html") && !strings.HasPrefix(contentType, "text/plain") {
maxReadBytes = maxWebFetchFileBytes
}
if resp.ContentLength > maxReadBytes {
return "", errors.New("response too large")
}
body, err := io.ReadAll(io.LimitReader(resp.Body, maxReadBytes))
if err != nil {View on GitHub (pinned to 251596fc0d)
Solutions
- From the SiYuan host, run `curl -I <rawURL>` to confirm reachability and isolate whether it is SiYuan-specific.
- Inspect HTTP_PROXY / HTTPS_PROXY / NO_PROXY env vars; fix or unset the broken proxy entry.
- Verify DNS: `nslookup <hostname>` or `getent hosts <hostname>`; if it fails, fix resolver or /etc/hosts.
- Retry once after a short delay for transient DNS/connection blips.
- If the cert is the issue, correct the system CA store rather than disabling verification.
Example fix
// before: opaque failure
out, err := util.WebFetch(raw, "markdown")
// after: preflight reachability so the user gets an actionable message
if _, lerr := net.LookupHost(hostnameOf(raw)); lerr != nil {
return fmt.Errorf("cannot resolve host %q: %w", hostnameOf(raw), lerr)
}
out, err := util.WebFetch(raw, "markdown") Defensive patterns
Strategy: retry
Validate before calling
// Cheap preflight: resolve the host before paying for a full GET.
func hostResolvable(rawURL string) error {
u, err := url.Parse(rawURL)
if err != nil || (u.Scheme != "http" && u.Scheme != "https") || u.Host == "" {
return errors.New("invalid URL")
}
if _, err := net.LookupHost(u.Hostname()); err != nil {
return fmt.Errorf("dns: %w", err)
}
return nil
} Type guard
// Distinguish the transport failure from the other WebFetch errors.
func isFetchFailed(err error) bool {
return err != nil && strings.HasPrefix(err.Error(), "fetch failed:")
} Try / catch
out, err := util.WebFetch(raw, "markdown")
if err != nil {
if isFetchFailed(err) {
// transient transport error: backoff and retry once
out, err = util.WebFetch(raw, "markdown")
}
}
if err != nil {
return err // surface remaining error to the user
} Prevention
- Validate the URL and resolve its host client-side before calling WebFetch to give a clearer error.
- Keep HTTP_PROXY/HTTPS_PROXY correct for the deployment environment.
- Treat fetch-failed as transient and retry once with backoff before giving up.
When it happens
Trigger: Calling util.WebFetch(url, format) where the host cannot be resolved (NXDOMAIN), the TCP connection is refused or times out, the TLS handshake fails (expired/self-signed cert), or an HTTP(S)_PROXY env var points at an unreachable proxy.
Common situations: Corporate proxy misconfiguration, target site is down, IPv6-only host with no v4 fallback, firewall egress block, expired certificate, air-gapped machine, transient DNS hiccup.
Related errors
- request failed: %s
- read body failed: %s
- download custom emoji failed with status %d
- get stage index failed
- OAuth token endpoint returned %s
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/31b104f071aaed86.
Report an issue: GitHub.