siyuan-note/siyuan · error

parse file annotation

Error message

parse file annotation [%s]: %w

What it means

The .sya file content must be a JSON object mapping annotation IDs to page descriptors. gulu.JSON.UnmarshalJSON failure is wrapped as this parse error and aborts the export. This means the sidecar exists but its content is not valid JSON of the expected shape.

Solutions

  1. Open the .sya file and fix or restore valid JSON of the expected {annotationID: {pages:[{index}], page}} shape
  2. Re-sync or restore the .sya from a version history snapshot
  3. Delete the corrupt sidecar and recreate the annotation in the PDF so a fresh .sya is written
  4. Check the file encoding is plain UTF-8 without BOM

Example fix

// before: broken sidecar
{"ann-1": {"page": oops
// after: valid sidecar
{"ann-1": {"page": 1}}
Defensive patterns

Strategy: validation

Validate before calling

// validate sidecar JSON shape before export
function isValidSya(text) {
  try {
    const obj = JSON.parse(text);
    return typeof obj === "object" && obj !== null &&
      Object.values(obj).every(v => v && (typeof v.page === "number" || Array.isArray(v.pages)));
  } catch { return false; }
}

Try / catch

try {
  await exportAnnotation(docID);
} catch (e) {
  if (/parse file annotation/.test(e.message)) {
    // fall back to a version-history snapshot of the .sya
    await restoreFileVersion(assetPath + ".sya");
  }
}

Prevention

When it happens

Trigger: Exporting an annotation where the .sya file was hand-edited and broke JSON syntax, truncated during transfer, saved with wrong encoding (BOM/UTF-16), or written by an incompatible tool version with a different schema.

Common situations: Manual editing of sidecar files; sync tools that merge .sya files textually; disk corruption; third-party annotation writers producing divergent JSON.

Understand the failure class

Background: JSON parse error: "Unexpected token" / "not valid JSON" / "failed to parse" — what JSON parsers are really complaining about — this error's family across 45 libraries.

Related errors


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

Appendix: source

Thrown at kernel/model/export.go:4390

	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]
	pageIndex := annotationData.Page
	if 0 < len(annotationData.Pages) {
		pageIndex = annotationData.Pages[0].Index
	}
	if !found || nil == pageIndex || *pageIndex < 0 {
		return fmt.Errorf("missing or invalid annotation [%s] in [%s]", annotationID, sya)
	}
	pageStr := strconv.Itoa(*pageIndex + 1)

	refText := n.TextMarkTextContent
	ext := filepath.Ext(p)
	file := strings.TrimPrefix(strings.TrimSuffix(p, ext), "assets/")
	// 仅剥离完整的节点 ID 后缀,无后缀文件及短文件名保持原样。
	if len(file) > 23 && file[len(file)-23] == '-' && ast.IsNodeIDPattern(file[len(file)-22:]) {
		file = file[:len(file)-23]
	}

View on GitHub (pinned to 9f775e8a12)