siyuan-note/siyuan · error

read file annotation

Error message

read file annotation [%s]: %w

What it means

After resolving the annotation asset, the kernel reads the sibling .sya annotation sidecar file with os.ReadFile. Any OS-level read failure (missing file, permission denied, path too long) is wrapped as this error and aborts the annotation export, keeping the source file untouched.

Solutions

  1. Check that a .sya file with the exact same name as the PDF exists next to it in assets/
  2. Fix filesystem permissions on the assets directory so the kernel process can read it
  3. Re-sync the notebook so the missing .sya sidecar is restored
  4. Recreate the annotation if the sidecar is permanently lost

Example fix

// before
assets/report.pdf          (no sidecar)
// after
assets/report.pdf
assets/report.pdf.sya      (restored via sync or re-created annotation)
Defensive patterns

Strategy: try-catch

Try / catch

try {
  await exportAnnotation(docID);
} catch (e) {
  if (/read file annotation/.test(e.message)) {
    // re-sync the notebook, then retry once
    await syncNotebook(boxID);
    return exportAnnotation(docID);
  }
  throw e;
}

Prevention

When it happens

Trigger: Exporting a file annotation whose <asset>.sya sidecar does not exist next to the PDF, or is unreadable due to filesystem permissions, sync placeholder files, or disk errors.

Common situations: PDF annotated elsewhere but the .sya file never synced; the sidecar was cleaned up by a sync tool; read-only mount or permission issue in the workspace data folder; case-sensitivity mismatch on Linux mounts.

Understand the failure class

Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.

Related errors


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

Appendix: source

Thrown at kernel/model/export.go:4374

	if err != nil {
		return fmt.Errorf("resolve file annotation asset [%s]: %w", assetLink, err)
	}
	sya := absPath + ".sya"
	// 以实际资源所属笔记本认证标注密文,读取失败时保留源文件并中止导出。
	assetBoxID := ExtractBoxIDFromAssetsPath(absPath)
	var dek []byte
	if IsEncryptedBox(assetBoxID) {
		HoldBoxReadLock(assetBoxID)
		defer ReleaseBoxReadLock(assetBoxID)
		dek, err = GetDEKIfUnlocked(assetBoxID)
		if err != nil {
			return err
		}
		defer clear(dek)
	}
	syaData, readErr := os.ReadFile(sya)
	if readErr != nil {
		return fmt.Errorf("read file annotation [%s]: %w", sya, readErr)
	}
	if nil != dek {
		plain, decErr := DecryptAsset(assetBoxID, filepath.Base(sya), dek, syaData)
		if decErr != nil {
			return fmt.Errorf("decrypt file annotation [%s]: %w", sya, decErr)
		}
		syaData = plain
	}
	syaJSON := map[string]struct {
		Pages []struct {
			Index *int `json:"index"`
		} `json:"pages"`
		Page *int `json:"page"`
	}{}
	if err = gulu.JSON.UnmarshalJSON(syaData, &syaJSON); err != nil {
		return fmt.Errorf("parse file annotation [%s]: %w", sya, err)
	}
	annotationData, found := syaJSON[annotationID]

View on GitHub (pinned to 9f775e8a12)