siyuan-note/siyuan · error

cannot parse document

Error message

cannot parse document %s: %v

What it means

After preliminary content checks, the document is parsed with dataparser.ParseJSONWithoutFix. If parsing fails or yields a nil tree/root, the scan reports 'cannot parse document' wrapping the underlying error. Unlike the raw json.Valid check, this catches structurally valid JSON that is not a valid document tree.

Solutions

  1. Read the wrapped %v cause to see why parsing failed and fix the document accordingly.
  2. Restore the document from history or sync snapshots; if it is disposable, delete it.
  3. Open and resave the document in the SiYuan app so the normal parser can repair it, then retry the relink.
  4. If it stems from a format-version mismatch, upgrade/downgrade SiYuan to a compatible version before scanning.
Defensive patterns

Strategy: try-catch

Try / catch

if err != nil && strings.HasPrefix(err.Error(), "cannot parse document") {
    // inspect wrapped cause, restore or repair the document
}

Prevention

When it happens

Trigger: A .sy file with valid JSON but a malformed document structure (missing root node, wrong node kinds) that ParseJSONWithoutFix cannot build into a tree; tree or tree.Root is nil after parsing; encountered during the relink notebook scan.

Common situations: Documents produced or mutated by third-party scripts that wrote plausible-but-invalid JSON; forward/backward format incompatibility after a version change; corruption that preserves JSON validity but breaks the AST invariants.

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/ee38d4f754aef503. Report an issue: GitHub.

Appendix: source

Thrown at kernel/model/asset_relink.go:188

				return fmt.Errorf("invalid document JSON: %s", absPath)
			}
			var header struct {
				ID         string `json:"ID"`
				Properties struct {
					Title string `json:"title"`
				} `json:"Properties"`
			}
			if readErr = json.Unmarshal(data, &header); readErr != nil {
				return readErr
			}
			titles[header.ID] = header.Properties.Title
			if !p.mayContainReferences(data) && !bytes.Contains(data, []byte("NodeAttributeView")) {
				return nil
			}
			p.parsedDocuments++
			tree, readErr := dataparser.ParseJSONWithoutFix(data, luteEngine.ParseOptions)
			if readErr != nil || tree == nil || tree.Root == nil {
				return fmt.Errorf("cannot parse document %s: %v", absPath, readErr)
			}
			rel, _ := filepath.Rel(boxDir, absPath)
			tree.Box, tree.Path = boxID, "/"+filepath.ToSlash(rel)
			tree.Root.Box, tree.Root.Path = tree.Box, tree.Path
			tree.HPath = "/" + tree.Root.IALAttr("title")
			if readErr = filesys.NormalizeTreeForRead(tree); readErr != nil {
				return readErr
			}
			if tree.Root.ID != strings.TrimSuffix(entry.Name(), ".sy") {
				return fmt.Errorf("document ID does not match path: %s", absPath)
			}
			titles[tree.Root.ID] = tree.Root.IALAttr("title")
			before := len(p.result.References)
			p.tree(tree, apicontract.AssetReference{Notebook: boxID, RootID: tree.Root.ID, Path: tree.Path})
			retain := false
			if len(p.result.References) > before {
				p.files = append(p.files, &assetRelinkFile{path: absPath, before: data, tree: tree, items: p.referenceItems(before)})
				retain = true

View on GitHub (pinned to 9f775e8a12)