{"record":{"id":"1185526d95f0ef0b","repo":"siyuan-note/siyuan","slug":"invalid-ocr-metadata","errorCode":null,"errorMessage":"invalid OCR metadata","messagePattern":"invalid OCR metadata","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/util/ocr_relink_batch.go","lineNumber":55,"sourceCode":"type assetTextIndexedValue struct {\n\tvalue    string\n\tconflict bool\n}\n\n// PrepareAssetTextRelinks 从同一份 OCR 快照计算所有映射，不写文件或内存缓存。\nfunc PrepareAssetTextRelinks(mappings []AssetTextRelinkMapping) (*AssetTextRelinkPlan, error) {\n\tassetsTextsLock.Lock()\n\tdefer assetsTextsLock.Unlock()\n\tp := &AssetTextRelinkPlan{mappings: mappings, Results: make([]AssetTextRelinkResult, len(mappings)),\n\t\ttexts: map[string]string{}, memory: maps.Clone(assetsTexts), values: make([]string, len(mappings)),\n\t\tpath: filepath.Join(GetDataAssetsAbsPath(), \"ocr-texts.json\")}\n\tdata, err := filelock.ReadFile(p.path)\n\tif err != nil && !os.IsNotExist(err) {\n\t\treturn nil, err\n\t}\n\tif err == nil {\n\t\tif err = json.Unmarshal(data, &p.texts); err != nil || p.texts == nil {\n\t\t\treturn nil, errors.New(\"invalid OCR metadata\")\n\t\t}\n\t\tp.original = data\n\t}\n\tmaps.Copy(p.texts, p.memory)\n\tindex := map[string]assetTextIndexedValue{}\n\tadd := func(key, value string) {\n\t\tif prior, found := index[key]; found {\n\t\t\tprior.conflict = prior.conflict || prior.value != value\n\t\t\tindex[key] = prior\n\t\t} else {\n\t\t\tindex[key] = assetTextIndexedValue{value: value}\n\t\t}\n\t}\n\tfor key, value := range p.texts {\n\t\tadd(key, value)\n\t\tparsed, parseErr := url.Parse(key)\n\t\tif parseErr == nil && !parsed.IsAbs() && parsed.Host == \"\" {\n\t\t\tcanonical := strings.TrimPrefix(parsed.Path, \"/\")","sourceCodeStart":37,"sourceCodeEnd":73,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/util/ocr_relink_batch.go#L37-L73","documentation":"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.","triggerScenarios":"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`.","commonSituations":"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.","solutions":["Validate the file: run it through a JSON parser to find the syntax error at data/assets/ocr-texts.json.","Fix the JSON syntax manually (it is a flat JSON object of asset-path → OCR-text string pairs).","If unrecoverable, restore ocr-texts.json from history (historyDir/assets/ocr-texts.json backups) or from sync snapshots.","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.","Re-run OCR (image OCR recognition) afterwards to rebuild entries if the file had to be recreated."],"exampleFix":"// before: unmarshalling broken file\n// data/assets/ocr-texts.json: {\"assets/foo.png\": \"text\",}\n// after: repair to valid JSON object or restore from backup\n// {\"assets/foo.png\": \"text\"}","handlingStrategy":"validation","validationCode":"data, err := os.ReadFile(filepath.Join(util.GetDataAssetsAbsPath(), \"ocr-texts.json\"))\nif err == nil {\n    var probe map[string]string\n    if json.Unmarshal(data, &probe) != nil || probe == nil {\n        // restore from history backup before calling PrepareAssetTextRelinks\n    }\n}","typeGuard":null,"tryCatchPattern":"plan, err := util.PrepareAssetTextRelinks(mappings)\nif err != nil && err.Error() == \"invalid OCR metadata\" {\n    // restore data/assets/ocr-texts.json from historyDir/assets/ocr-texts.json, then retry\n    return restoreOCRBackupAndRetry(mappings)\n}","preventionTips":["Never hand-edit ocr-texts.json without validating with a JSON parser afterwards","Restore from historyDir/assets/ocr-texts.json backups after a crash","Treat any truncation symptom (sync conflicts, disk-full writes) as a signal to validate the file","Ensure the file is UTF-8 without BOM"],"tags":["json","ocr","file-corruption","unmarshal"],"backgroundTag":"json-unmarshal-failed","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}