siyuan-note/siyuan · error

read body failed

Error message

read body failed: %s

What it means

fetchBytes reads the response body through io.LimitReader capped at maxSkillDownloadBytes+1; if the read itself errors (connection reset mid-transfer, timeout while streaming, decompression failure), it returns 'read body failed: <cause>'. The download started successfully but the body could not be fully received.

Solutions

  1. Inspect the wrapped cause and simply retry the install — most mid-transfer failures are transient
  2. Check network stability; prefer wired/stable connections for large archives
  3. If a proxy is in the path, check its idle/transfer timeouts
  4. If it persists for one host only, try an alternative mirror or direct URL for the same skill
Defensive patterns

Strategy: retry

Try / catch

try {
  await installSkill(src)
} catch (e) {
  if (String(e).startsWith("read body failed:")) {
    await retryWithBackoff(() => installSkill(src), { retries: 3 })
  }
}

Prevention

When it happens

Trigger: io.ReadAll fails mid-stream after a 2xx response — server closed the connection prematurely, network dropped during transfer, or the underlying reader returned a non-EOF error.

Common situations: Flaky Wi-Fi or mobile connection during a large download; proxy dropping long-running transfers; server-side aborts; TLS truncation.

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


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/2006269e1077bf57. Report an issue: GitHub.

Appendix: source

Thrown at kernel/util/skill.go:716

	return nil, "", err
}

// fetchBytes 执行带大小限制的 GET
func fetchBytes(rawURL string) (data []byte, contentType string, err error) {
	resp, err := httpclient.NewBrowserRequest().Get(rawURL)
	if err != nil {
		return nil, "", errors.New("download failed: " + err.Error())
	}
	defer resp.Body.Close()

	if resp.StatusCode >= 400 {
		return nil, "", fmt.Errorf("download failed: HTTP %d", resp.StatusCode)
	}

	contentType = resp.Header.Get("Content-Type")
	body, err := io.ReadAll(io.LimitReader(resp.Body, maxSkillDownloadBytes+1))
	if err != nil {
		return nil, "", errors.New("read body failed: " + err.Error())
	}
	if len(body) > maxSkillDownloadBytes {
		return nil, "", errors.New("skill source too large (limit 10MB)")
	}
	return body, contentType, nil
}

// installFromZip 解压 zip 并安装其中的 skill
func installFromZip(data []byte) (*InstallSkillResult, error) {
	tmpRoot := filepath.Join(TempDir, "ai", "skill-install", gulu.Rand.String(7))
	if err := os.MkdirAll(tmpRoot, 0755); err != nil {
		return nil, err
	}
	defer os.RemoveAll(tmpRoot)

	zipPath := filepath.Join(tmpRoot, "src.zip")
	if err := os.WriteFile(zipPath, data, 0644); err != nil {
		return nil, err

View on GitHub (pinned to 9f775e8a12)