siyuan-note/siyuan · error
invalid OCR mapping index
Error message
invalid OCR mapping index
What it means
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.
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.
Example fix
// before
plan, _ := util.PrepareAssetTextRelinks(mappings)
plan.Save(historyDir, []int{0}) // panics-free but errors if mappings is empty
// after
var selected []int
for i := range plan.Results {
if plan.Results[i].Reason == "" && plan.Results[i].Changed {
selected = append(selected, i)
}
}
plan.Save(historyDir, selected) Defensive patterns
Strategy: validation
Validate before calling
func validIndices(plan *util.AssetTextRelinkPlan, selected []int) bool {
for _, i := range selected {
if i < 0 || i >= len(plan.Results) {
return false
}
}
return true
} Try / catch
if changed, err := plan.Save(historyDir, selected); err != nil && err.Error() == "invalid OCR mapping index" {
// rebuild selected from plan.Results and retry once
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- --attr is required (format: name=value)
- cannot rebuild encrypted indexes
- Content block with id
- duplicate encrypted document ID
- encrypted document root ID does not match filename
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/a5047b7e1a4660fd.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/util/ocr_relink_batch.go:126
for _, i := range group {
conflict = conflict || p.values[i] != p.values[group[0]]
}
if conflict {
for _, i := range group {
p.Results[i].Reason = "ocr_target_conflict"
}
}
}
return p, nil
}
// Save 将通过预检的映射合并为一次保存,保留源键;同值重试不生成历史或写入文件。
func (p *AssetTextRelinkPlan) Save(historyDir string, selected []int) ([]int, error) {
var changed []int
texts := maps.Clone(p.texts)
for _, i := range selected {
if i < 0 || i >= len(p.Results) {
return nil, errors.New("invalid OCR mapping index")
}
result := p.Results[i]
if result.Reason != "" {
return nil, errors.New(result.Reason)
}
if !result.Changed {
continue
}
texts[(&url.URL{Path: p.mappings[i].NewPath}).EscapedPath()] = p.values[i]
changed = append(changed, i)
}
if len(changed) == 0 {
return nil, nil
}
assetsTextsLock.Lock()
defer assetsTextsLock.Unlock()
if !maps.Equal(p.memory, assetsTexts) {
return nil, errors.New("OCR metadata changed during asset relink")View on GitHub (pinned to 9f775e8a12)