{"record":{"id":"a5047b7e1a4660fd","repo":"siyuan-note/siyuan","slug":"invalid-ocr-mapping-index","errorCode":null,"errorMessage":"invalid OCR mapping index","messagePattern":"invalid OCR mapping index","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/util/ocr_relink_batch.go","lineNumber":126,"sourceCode":"\t\tfor _, i := range group {\n\t\t\tconflict = conflict || p.values[i] != p.values[group[0]]\n\t\t}\n\t\tif conflict {\n\t\t\tfor _, i := range group {\n\t\t\t\tp.Results[i].Reason = \"ocr_target_conflict\"\n\t\t\t}\n\t\t}\n\t}\n\treturn p, nil\n}\n\n// Save 将通过预检的映射合并为一次保存，保留源键；同值重试不生成历史或写入文件。\nfunc (p *AssetTextRelinkPlan) Save(historyDir string, selected []int) ([]int, error) {\n\tvar changed []int\n\ttexts := maps.Clone(p.texts)\n\tfor _, i := range selected {\n\t\tif i < 0 || i >= len(p.Results) {\n\t\t\treturn nil, errors.New(\"invalid OCR mapping index\")\n\t\t}\n\t\tresult := p.Results[i]\n\t\tif result.Reason != \"\" {\n\t\t\treturn nil, errors.New(result.Reason)\n\t\t}\n\t\tif !result.Changed {\n\t\t\tcontinue\n\t\t}\n\t\ttexts[(&url.URL{Path: p.mappings[i].NewPath}).EscapedPath()] = p.values[i]\n\t\tchanged = append(changed, i)\n\t}\n\tif len(changed) == 0 {\n\t\treturn nil, nil\n\t}\n\tassetsTextsLock.Lock()\n\tdefer assetsTextsLock.Unlock()\n\tif !maps.Equal(p.memory, assetsTexts) {\n\t\treturn nil, errors.New(\"OCR metadata changed during asset relink\")","sourceCodeStart":108,"sourceCodeEnd":144,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/util/ocr_relink_batch.go#L108-L144","documentation":"AssetTextRelinkPlan.Save validates each index in the selected slice against the plan's Results slice before applying it. An index outside [0, len(Results)-1] means the caller is referencing a mapping that does not exist in the plan produced by PrepareAssetTextRelinks, so Save aborts with \"invalid OCR mapping index\" instead of panicking.","triggerScenarios":"Calling plan.Save(historyDir, selected) with a negative index, an index >= the number of mappings passed to PrepareAssetTextRelinks, or indices from a different/stale plan object than the one whose Results slice is being indexed.","commonSituations":"Hard-coded indices like []int{0} reused with an empty mappings slice; building a selected list from user checkboxes without clamping after the mapping list changed size; reusing indices captured from an earlier plan after re-running PrepareAssetTextRelinks with fewer mappings.","solutions":["Ensure selected indices come from the same plan returned by PrepareAssetTextRelinks and satisfy 0 <= i < len(plan.Results).","Check that the mappings slice passed to PrepareAssetTextRelinks was non-empty before selecting index 0.","Clamp or filter the selected slice: keep only indices within range before calling Save.","If indices came from a previous plan, rebuild them from the current plan's Results."],"exampleFix":"// before\nplan, _ := util.PrepareAssetTextRelinks(mappings)\nplan.Save(historyDir, []int{0}) // panics-free but errors if mappings is empty\n// after\nvar selected []int\nfor i := range plan.Results {\n    if plan.Results[i].Reason == \"\" && plan.Results[i].Changed {\n        selected = append(selected, i)\n    }\n}\nplan.Save(historyDir, selected)","handlingStrategy":"validation","validationCode":"func validIndices(plan *util.AssetTextRelinkPlan, selected []int) bool {\n    for _, i := range selected {\n        if i < 0 || i >= len(plan.Results) {\n            return false\n        }\n    }\n    return true\n}","typeGuard":null,"tryCatchPattern":"if changed, err := plan.Save(historyDir, selected); err != nil && err.Error() == \"invalid OCR mapping index\" {\n    // rebuild selected from plan.Results and retry once\n}","preventionTips":["Derive selected indices by iterating len(plan.Results), never hard-code them","Use one plan object end-to-end; never mix indices across plans","Guard against empty mappings before selecting index 0","Filter selections at the source (checkbox rows) with a bounds check"],"tags":["index","bounds","ocr","argument-validation"],"backgroundTag":"index-out-of-range","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"}