{"record":{"id":"31b104f071aaed86","repo":"siyuan-note/siyuan","slug":"fetch-failed-s","errorCode":null,"errorMessage":"fetch failed: %s","messagePattern":"fetch failed: (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/util/webfetch.go","lineNumber":56,"sourceCode":"\tmaxWebFetchChars     = 50000\n)\n\nfunc WebFetch(rawURL, format string) (string, error) {\n\tu, err := url.Parse(rawURL)\n\tif err != nil || (u.Scheme != \"http\" && u.Scheme != \"https\") {\n\t\treturn \"\", errors.New(\"URL must start with http:// or https://\")\n\t}\n\tif u.Host == \"\" {\n\t\treturn \"\", errors.New(\"URL has no host\")\n\t}\n\n\tif err := CheckHostSSRF(u.Hostname()); err != nil {\n\t\treturn \"\", err\n\t}\n\n\tresp, err := httpclient.NewBrowserRequest().Get(rawURL)\n\tif err != nil {\n\t\treturn \"\", errors.New(\"fetch failed: \" + err.Error())\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode >= 400 {\n\t\treturn \"\", fmt.Errorf(\"HTTP %d\", resp.StatusCode)\n\t}\n\n\tcontentType := resp.Header.Get(\"Content-Type\")\n\tmaxReadBytes := int64(maxWebFetchBytes)\n\tif !strings.HasPrefix(contentType, \"text/html\") && !strings.HasPrefix(contentType, \"text/plain\") {\n\t\tmaxReadBytes = maxWebFetchFileBytes\n\t}\n\tif resp.ContentLength > maxReadBytes {\n\t\treturn \"\", errors.New(\"response too large\")\n\t}\n\n\tbody, err := io.ReadAll(io.LimitReader(resp.Body, maxReadBytes))\n\tif err != nil {","sourceCodeStart":38,"sourceCodeEnd":74,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/util/webfetch.go#L38-L74","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: opaque failure\nout, err := util.WebFetch(raw, \"markdown\")\n\n// after: preflight reachability so the user gets an actionable message\nif _, lerr := net.LookupHost(hostnameOf(raw)); lerr != nil {\n    return fmt.Errorf(\"cannot resolve host %q: %w\", hostnameOf(raw), lerr)\n}\nout, err := util.WebFetch(raw, \"markdown\")","handlingStrategy":"retry","validationCode":"// Cheap preflight: resolve the host before paying for a full GET.\nfunc hostResolvable(rawURL string) error {\n    u, err := url.Parse(rawURL)\n    if err != nil || (u.Scheme != \"http\" && u.Scheme != \"https\") || u.Host == \"\" {\n        return errors.New(\"invalid URL\")\n    }\n    if _, err := net.LookupHost(u.Hostname()); err != nil {\n        return fmt.Errorf(\"dns: %w\", err)\n    }\n    return nil\n}","typeGuard":"// Distinguish the transport failure from the other WebFetch errors.\nfunc isFetchFailed(err error) bool {\n    return err != nil && strings.HasPrefix(err.Error(), \"fetch failed:\")\n}","tryCatchPattern":"out, err := util.WebFetch(raw, \"markdown\")\nif err != nil {\n    if isFetchFailed(err) {\n        // transient transport error: backoff and retry once\n        out, err = util.WebFetch(raw, \"markdown\")\n    }\n}\nif err != nil {\n    return err // surface remaining error to the user\n}","preventionTips":["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."],"tags":["network","http","fetch","transport"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}