siyuan-note/siyuan · error
invalid annotation file
Error message
invalid annotation file
What it means
PDF annotations are stored as <pdf>.sya files next to the PDF and must be valid, non-encrypted JSON. During scanAnnotation, if the source .sya file exists but its bytes are not valid JSON or look like ciphertext, the scan aborts because annotation relinking requires parsing and copying this data.
Solutions
- Restore the .sya file from history/backup or from the sync source
- Verify the file with a JSON validator after restoring before re-running the scan
- If annotations are unrecoverable, delete the .sya file so annotations are reported as missing instead of failing the scan
- Check the sync/encryption tooling so .sya files are transported as plaintext JSON
Example fix
// before
// assets/paper.pdf.sya contains non-JSON ciphertext
// after
cp("history/.../assets/paper.pdf.sya", "data/assets/paper.pdf.sya")
// verify: json.Valid(readFile(path)) == true Defensive patterns
Strategy: validation
Validate before calling
// validate the .sya annotation file before relinking
data, err := os.ReadFile(pdfPath + ".sya")
if err != nil || !json.Valid(data) {
// restore from backup or remove stale annotations
} Try / catch
// handle invalid annotation data gracefully
if err := plan.Scan(); err != nil {
if err.Error() == "invalid annotation file" {
// restore <pdf>.sya from history, then retry
}
return err
} Prevention
- Do not modify or encrypt .sya files externally
- Ensure sync transports .sya files verbatim as plaintext JSON
- Keep backups of .sya files alongside their PDFs
When it happens
Trigger: scanMetadata calls scanAnnotation for a PDF asset that has annotations; filelock.ReadFile returns data where json.Valid(data) is false or util.IsCiphertext(data) is true for the <old>.sya file.
Common situations: The .sya file was truncated by a crashed write; it was encrypted/obfuscated by an external tool or sync layer; the file was replaced by unrelated binary content; corruption during an interrupted sync.
Understand the failure class
Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.
Related errors
- Conf.Language(317)
- encrypted attribute view in global storage
- marshal notebook crypt backup failed
- marshal notebook crypto backup failed
- PDF page view is unavailable
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/23b4e9ec89d5e299.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/asset_relink.go:444
if _, statErr := os.Stat(source); statErr == nil {
if err := validateRelinkStoragePath(source); err != nil {
return err
}
}
data, err := filelock.ReadFile(source)
if os.IsNotExist(err) {
for i := range p.result.References {
if p.result.References[i].Type == "annotation" && p.result.References[i].OldPath == p.oldPath {
p.result.References[i].Relinkable, p.result.References[i].Reason = false, "annotation_file_missing"
}
}
return nil
}
if err != nil {
return err
}
if !json.Valid(data) || util.IsCiphertext(data) {
return errors.New("invalid annotation file")
}
ref := apicontract.AssetReference{OldPath: p.oldPath, Type: "annotation-file", Path: p.oldPath + ".sya", Reference: p.oldPath, Relinkable: true}
if p.newPath != "" {
ref.Replacement = p.newPath
if !strings.EqualFold(filepath.Ext(p.newPath), ".pdf") {
ref.Relinkable, ref.Reason = false, "annotation_requires_pdf"
} else if target, readErr := filelock.ReadFile(newAbs + ".sya"); readErr == nil {
if !bytes.Equal(data, target) {
ref.Relinkable, ref.Reason = false, "annotation_target_conflict"
}
} else if !os.IsNotExist(readErr) {
return readErr
} else {
items := p.itemsForPath(p.oldPath)
p.files = append(p.files, &assetRelinkFile{path: source, before: data, backupOnly: true, items: items})
p.files = append(p.files, &assetRelinkFile{path: newAbs + ".sya", after: data, items: items})
}
}View on GitHub (pinned to 9f775e8a12)