siyuan-note/siyuan · error
%s
Error message
%s
What it means
During Save, each selected mapping is re-checked: if its pre-computed Result carries a non-empty Reason (ocr_source_conflict or ocr_target_conflict), Save refuses to apply it and returns that reason as an error. The plan is atomic — one conflicted mapping aborts the whole save — because applying it would overwrite differing OCR recognition results.
Solutions
- Filter selected indices to those with Results[i].Reason == "" before calling Save.
- For ocr_target_conflict, choose a different NewPath or manually reconcile the target's OCR value in ocr-texts.json.
- For ocr_source_conflict, clean up duplicate/conflicting keys for the source path in data/assets/ocr-texts.json first.
- Run PrepareAssetTextRelinks again after manual edits — Reasons are computed against a snapshot and a fresh plan may now pass.
- Handle conflicts per mapping instead of all-or-nothing by splitting the batch into conflicted and clean groups.
Example fix
// before: pass all indices regardless of conflict
plan.Save(historyDir, allIndices)
// after: only save entries that passed the pre-check
var ok []int
for _, i := range allIndices {
if plan.Results[i].Reason == "" {
ok = append(ok, i)
}
}
plan.Save(historyDir, ok) Defensive patterns
Strategy: validation
Validate before calling
var selected []int
for i, r := range plan.Results {
if r.Reason == "" && r.Changed {
selected = append(selected, i)
}
}
// selected is guaranteed Save-safe for these indices Try / catch
if _, err := plan.Save(historyDir, selected); err != nil {
if err.Error() == "ocr_source_conflict" || err.Error() == "ocr_target_conflict" {
// report per-mapping conflicts to the user; re-prepare after edits
}
} Prevention
- Only pass indices whose Results[i].Reason is empty into Save
- Show conflict reasons in the UI before committing a batch rename
- Re-run PrepareAssetTextRelinks after any manual ocr-texts.json edit
- Remember Save is all-or-nothing: split conflicted rows out of the batch
When it happens
Trigger: Calling plan.Save on a plan where any selected index has Results[i].Reason != "" — i.e. the caller skipped inspecting Results and passed indices flagged as conflicted by PrepareAssetTextRelinks (source path has conflicting OCR values, or the target already exists with a different value, or multiple sources merge into one target with different values).
Common situations: Batch rename UI selecting all rows including conflicted ones; CopyAssetTextForRelink-style single mappings where the dry-run step was skipped; merging folders where several renamed assets map onto one target name with divergent OCR text.
Understand the failure class
Background: Conflicting config options: "cannot be used together" — configuration validation errors across open-source libraries — this error's family across 162 libraries.
Related errors
- %s
- ambiguous asset path
- attribute view custom color
- attribute view custom color
- block changed since conversion
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/05ea64471cf9ffd7.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/util/ocr_relink_batch.go:130
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")
}
backup, err := json.MarshalIndent(p.texts, "", " ")
if err != nil {
return nil, errView on GitHub (pinned to 9f775e8a12)