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
- Open the .sya file and fix or restore valid JSON of the expected {annotationID: {pages:[{index}], page}} shape
- Re-sync or restore the .sya from a version history snapshot
- Delete the corrupt sidecar and recreate the annotation in the PDF so a fresh .sya is written
- 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
- Do not hand-edit .sya files; use the app's annotation UI
- Exclude .sya files from textual merge tools / sync conflict resolution
- Keep sidecar files as UTF-8 without BOM
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
- decrypt file annotation
- invalid file annotation reference
- missing or invalid annotation
- parse imported notebook conf
- parse json [ ] to tree failed
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)