siyuan-note/siyuan · error

invalid OCR metadata

Error message

invalid OCR metadata

What it means

PrepareAssetTextRelinks reads data/assets/ocr-texts.json and unmarshals it into a map[string]string. If the file exists but its content is not valid JSON, or parses to JSON null (which would leave p.texts nil), the function refuses to proceed with "invalid OCR metadata" rather than silently discarding or rebuilding OCR data.

Solutions

  1. Validate the file: run it through a JSON parser to find the syntax error at data/assets/ocr-texts.json.
  2. Fix the JSON syntax manually (it is a flat JSON object of asset-path → OCR-text string pairs).
  3. If unrecoverable, restore ocr-texts.json from history (historyDir/assets/ocr-texts.json backups) or from sync snapshots.
  4. Do NOT simply delete the file: only do so if losing OCR text is acceptable, as it makes the file 'not exist' and resets metadata.
  5. Re-run OCR (image OCR recognition) afterwards to rebuild entries if the file had to be recreated.

Example fix

// before: unmarshalling broken file
// data/assets/ocr-texts.json: {"assets/foo.png": "text",}
// after: repair to valid JSON object or restore from backup
// {"assets/foo.png": "text"}
Defensive patterns

Strategy: validation

Validate before calling

data, err := os.ReadFile(filepath.Join(util.GetDataAssetsAbsPath(), "ocr-texts.json"))
if err == nil {
    var probe map[string]string
    if json.Unmarshal(data, &probe) != nil || probe == nil {
        // restore from history backup before calling PrepareAssetTextRelinks
    }
}

Try / catch

plan, err := util.PrepareAssetTextRelinks(mappings)
if err != nil && err.Error() == "invalid OCR metadata" {
    // restore data/assets/ocr-texts.json from historyDir/assets/ocr-texts.json, then retry
    return restoreOCRBackupAndRetry(mappings)
}

Prevention

When it happens

Trigger: Calling PrepareAssetTextRelinks (directly or via CopyAssetTextForRelink / scanMetadata) when data/assets/ocr-texts.json contains malformed JSON (truncated write, manual edit, encoding error) or literal `null`.

Common situations: A crash or power loss during a previous write left a truncated file; the user hand-edited ocr-texts.json and broke JSON syntax; the file was saved with a BOM or in a non-UTF-8 encoding; sync restored a partially written copy.

Understand the failure class

Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.

Related errors


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

Appendix: source

Thrown at kernel/util/ocr_relink_batch.go:55

type assetTextIndexedValue struct {
	value    string
	conflict bool
}

// PrepareAssetTextRelinks 从同一份 OCR 快照计算所有映射,不写文件或内存缓存。
func PrepareAssetTextRelinks(mappings []AssetTextRelinkMapping) (*AssetTextRelinkPlan, error) {
	assetsTextsLock.Lock()
	defer assetsTextsLock.Unlock()
	p := &AssetTextRelinkPlan{mappings: mappings, Results: make([]AssetTextRelinkResult, len(mappings)),
		texts: map[string]string{}, memory: maps.Clone(assetsTexts), values: make([]string, len(mappings)),
		path: filepath.Join(GetDataAssetsAbsPath(), "ocr-texts.json")}
	data, err := filelock.ReadFile(p.path)
	if err != nil && !os.IsNotExist(err) {
		return nil, err
	}
	if err == nil {
		if err = json.Unmarshal(data, &p.texts); err != nil || p.texts == nil {
			return nil, errors.New("invalid OCR metadata")
		}
		p.original = data
	}
	maps.Copy(p.texts, p.memory)
	index := map[string]assetTextIndexedValue{}
	add := func(key, value string) {
		if prior, found := index[key]; found {
			prior.conflict = prior.conflict || prior.value != value
			index[key] = prior
		} else {
			index[key] = assetTextIndexedValue{value: value}
		}
	}
	for key, value := range p.texts {
		add(key, value)
		parsed, parseErr := url.Parse(key)
		if parseErr == nil && !parsed.IsAbs() && parsed.Host == "" {
			canonical := strings.TrimPrefix(parsed.Path, "/")

View on GitHub (pinned to 9f775e8a12)