siyuan-note/siyuan · error
%s
Error message
%s
What it means
CopyAssetTextForRelink performs a pre-checked relink of OCR metadata from an old asset path to a new one. If the pre-check (PrepareAssetTextRelinks) records a Reason on the result — e.g. ocr_source_conflict or ocr_target_conflict — the copy is refused and that reason is surfaced as the returned error. This means relinking would overwrite or merge inconsistent OCR recognition results, so the library aborts to preserve metadata integrity.
Solutions
- Inspect the error string: it is exactly the plan's Reason (ocr_source_conflict or ocr_target_conflict).
- For ocr_source_conflict, clean up data/assets/ocr-texts.json so the old path has a single consistent OCR value, then retry.
- For ocr_target_conflict, pick a different NewPath or manually reconcile the OCR text for the target in ocr-texts.json before relinking.
- Use dryRun=true first and inspect the returned Reason before attempting a real copy.
- If the old asset has no OCR entry, this path returns exists=false with no error; only handle the error when Reason applies.
Example fix
// before: blind copy
exists, err := util.CopyAssetTextForRelink(oldPath, newPath, historyDir, false)
// after: dry-run pre-check and handle conflicts
if exists, err := util.CopyAssetTextForRelink(oldPath, newPath, historyDir, true); err != nil {
log.Warnf("OCR relink refused (%s)", err) // e.g. ocr_target_conflict
} else if exists {
util.CopyAssetTextForRelink(oldPath, newPath, historyDir, false)
} Defensive patterns
Strategy: validation
Validate before calling
// Pre-check with dryRun before committing
exists, err := util.CopyAssetTextForRelink(oldPath, newPath, historyDir, true)
if err != nil {
// Reason is ocr_source_conflict or ocr_target_conflict — surface to user, do not retry blindly
return fmt.Errorf("OCR relink pre-check failed: %w", err)
} Try / catch
if _, err := util.CopyAssetTextForRelink(old, new, histDir, false); err != nil {
switch {
case strings.Contains(err.Error(), "ocr_source_conflict"):
// clean source keys in ocr-texts.json, then retry
case strings.Contains(err.Error(), "ocr_target_conflict"):
// pick another NewPath or reconcile target text
default:
return err
}
} Prevention
- Always dryRun=true first and inspect the Reason before the real copy
- Keep ocr-texts.json free of duplicate/conflicting keys for the same asset
- Avoid renaming assets to names that previously held different OCR text
- Check result.Exists: relinking an asset with no OCR entry never errors, so treat error as conflict only
When it happens
Trigger: Calling CopyAssetTextForRelink(oldPath, newPath, historyDir, dryRun) when (a) the old path's OCR text conflicts with other entries keyed to the same canonical path (ocr_source_conflict), or (b) the new path already exists in the index with a different OCR value or is itself conflicted / is the merge target of sources with differing values (ocr_target_conflict).
Common situations: Renaming assets after an OCR re-run left stale keys under both the raw and canonical forms of a path; renaming an asset to a name that previously held different OCR text (e.g. after restoring a backup); batch renames where multiple sources funnel into one target with divergent recognition results.
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
- asset escapes its directory
- asset must be a regular file
- attribute view custom color
- attribute view custom color
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/01a0d014946622f9.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/util/ocr_relink.go:19
// SiYuan - From thought to insight, with agents
// Copyright (c) 2020-present, b3log.org
// SPDX-License-Identifier: AGPL-3.0-or-later
package util
import (
"errors"
)
// CopyAssetTextForRelink 复制 OCR 元数据,保留源资源的识别结果,并拒绝覆盖不同的目标结果。
func CopyAssetTextForRelink(oldPath, newPath, historyDir string, dryRun bool) (exists bool, err error) {
plan, err := PrepareAssetTextRelinks([]AssetTextRelinkMapping{{OldPath: oldPath, NewPath: newPath}})
if err != nil {
return false, err
}
result := plan.Results[0]
if result.Reason != "" {
return result.Exists, errors.New(result.Reason)
}
if !dryRun {
_, err = plan.Save(historyDir, []int{0})
}
return result.Exists, err
}
View on GitHub (pinned to 9f775e8a12)