siyuan-note/siyuan · error

write file failed: %s

Error message

write file failed: %s

What it means

Returned at webfetch.go:86 when os.WriteFile(filePath, body, 0644) fails after the import directory was created successfully. The file payload was downloaded but could not be persisted. The %s is the OS write error.

Source

Thrown at kernel/util/webfetch.go:86

	}
	if resp.ContentLength > maxReadBytes {
		return "", errors.New("response too large")
	}

	body, err := io.ReadAll(io.LimitReader(resp.Body, maxReadBytes))
	if err != nil {
		return "", errors.New("read body failed: " + err.Error())
	}

	if !strings.HasPrefix(contentType, "text/html") && !strings.HasPrefix(contentType, "text/plain") {
		importDir := filepath.Join(TempDir, "import")
		if merr := os.MkdirAll(importDir, 0755); merr != nil {
			return "", errors.New("create import dir failed: " + merr.Error())
		}
		filename := extractFilename(rawURL, contentType)
		filePath := filepath.Join(importDir, filename)
		if werr := os.WriteFile(filePath, body, 0644); werr != nil {
			return "", errors.New("write file failed: " + werr.Error())
		}
		return fmt.Sprintf("Saved to: %s (%d bytes)", filePath, len(body)), nil
	}

	htmlStr := string(body)

	isHTML := strings.HasPrefix(contentType, "text/html")
	if !isHTML {
		return truncateRunes(htmlStr, maxWebFetchChars), nil
	}

	if htmlStr == "" {
		return "", nil
	}

	engine := NewLute()
	var result string
	switch format {

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Free disk space on the target volume.
  2. Verify write permission and that no directory already uses the target filename.
  3. Retry — transient AV locks often clear on the second attempt.
  4. Check the filename produced by extractFilename for unexpected characters.
Defensive patterns

Strategy: validation

Validate before calling

// Best-effort free-space check on the import volume.
func importVolumeHasSpace() error {
    dir := filepath.Join(util.TempDir, "import")
    if err := os.MkdirAll(dir, 0755); err != nil {
        return err
    }
    var stat syscall.Statfs_t
    if err := syscall.Statfs(dir, &stat); err != nil {
        return err
    }
    if uint64(stat.Bavail)*uint64(stat.Bsize) < uint64(10*1024*1024) {
        return errors.New("less than 10 MiB free")
    }
    return nil
}

Type guard

func isWriteFileFailed(err error) bool {
    return err != nil && strings.HasPrefix(err.Error(), "write file failed:")
}

Try / catch

out, err := util.WebFetch(raw, "markdown")
if err != nil && isWriteFileFailed(err) {
    // often transient (AV lock on Windows) — retry once
    out, err = util.WebFetch(raw, "markdown")
}

Prevention

When it happens

Trigger: Disk full at the moment of writing, permission denied on the target path, a directory already exists at the target filename, path-too-long, antivirus locking the file (Windows), or a read-only filesystem mounted between mkdir and write.

Common situations: Disk filling up during a large download, filename extracted by extractFilename colliding with an existing directory, security software scanning/locking newly written files.

Related errors


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