siyuan-note/siyuan · error
fetch failed
Error message
fetch failed: %s
What it means
After SSRF validation passes, WebFetch performs ssrfSafeClient.Get(rawURL); any transport-level failure (DNS resolution, TCP connect, TLS handshake, timeout) is wrapped into this error with the underlying net/http message appended. It means the kernel could not complete the HTTP request at all — no HTTP status was received.
Solutions
- Read the wrapped cause after 'fetch failed: ' and fix accordingly (DNS, timeout, TLS)
- Verify the host resolves and is reachable: curl -v the same URL from the kernel host
- Check whether the URL points at a private/loopback address the SSRF-safe client refuses
- Increase tolerance for slow sites by checking client timeouts; retry transient network failures
Example fix
// before
_, err := WebFetch(target, "markdown") // opaque failure
// after
_, err := WebFetch(target, "markdown")
if err != nil && strings.HasPrefix(err.Error(), "fetch failed: ") {
log.Printf("transport error for %s: %v", target, err) // inspect wrapped cause
} Defensive patterns
Strategy: retry
Validate before calling
// Pre-check reachability cheaply
if net.ParseIP(host) != nil && isPrivateIP(host) {
return errors.New("target is a private address; will be refused")
} Try / catch
content, err := WebFetch(url, format)
if err != nil && strings.HasPrefix(err.Error(), "fetch failed: ") {
// transport-level failure; inspect wrapped cause, retry with backoff for timeouts/resets
} Prevention
- Confirm the target resolves from the kernel host before blaming WebFetch
- Keep an eye on TLS certificate expiry for internal hosts
- Retry transient transport errors with exponential backoff
- Expect SSRF-safe dialing to refuse private/loopback targets
When it happens
Trigger: Host does not resolve (DNS failure), connection refused/timed out, TLS certificate errors, proxy failures, or the SSRF-safe client's dial policy blocking the connection (private IP re-check at connect time).
Common situations: Fetching an intranet hostname from a machine without VPN access; expired or self-signed certificates; the target site is down; corporate proxy not configured; DNS blocked in sandboxed environments.
Understand the failure class
Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.
Related errors
- authentication probe returned HTTP " + response.status
- boot progress request returned HTTP " + response.status
- discover OIDC provider failed
- download custom emoji failed
- download custom emoji failed with status
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/31b104f071aaed86.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/util/webfetch.go:55
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 := ssrfSafeClient.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 9f775e8a12)