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

  1. From the SiYuan host, run `curl -I <rawURL>` to confirm reachability and isolate whether it is SiYuan-specific.
  2. Inspect HTTP_PROXY / HTTPS_PROXY / NO_PROXY env vars; fix or unset the broken proxy entry.
  3. Verify DNS: `nslookup <hostname>` or `getent hosts <hostname>`; if it fails, fix resolver or /etc/hosts.
  4. Retry once after a short delay for transient DNS/connection blips.
  5. 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

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


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