siyuan-note/siyuan · error
duplicate oldPath
Error message
duplicate oldPath: %s
What it means
The same oldPath may appear only once in a batch of mappings. Duplicate oldPath values would make routing ambiguous (two different targets for one source), so the plan constructor collects the error and rejects the whole batch with this message.
Solutions
- Deduplicate mappings by OldPath before constructing the batch (keep the last/newest rule)
- Detect duplicates in the UI when a row is added and merge or reject it there
- Normalize paths (case, separators, leading 'assets/') before dedup so equivalent spellings collapse
Example fix
// before
mappings := append(perNotebookA, perNotebookB...) // may contain dup oldPath
model.RelinkAssets(ctx, mappings, false)
// after
seen := map[string]bool{}
var unique []apicontract.AssetRelinkMapping
for _, m := range mappings {
if seen[m.OldPath] { continue }
seen[m.OldPath] = true
unique = append(unique, m)
}
model.RelinkAssets(ctx, unique, false) Defensive patterns
Strategy: validation
Validate before calling
seen := map[string]bool{}
for _, m := range mappings {
if seen[m.OldPath] { return fmt.Errorf("duplicate oldPath %q", m.OldPath) }
seen[m.OldPath] = true
} Prevention
- Deduplicate mappings by normalized oldPath before batching
- Merge duplicate UI rows on add
- Normalize separators/case before comparing paths
When it happens
Trigger: Calling RelinkAssets or FindAssetReferencesBatch with two mappings whose normalized OldPath is identical, e.g. [{a.png -> b.png}, {a.png -> c.png}], or duplicated entries from concatenating request lists.
Common situations: Batch UI allowing the user to add the same source row twice; scripts merging per-notebook mapping lists without dedup; copy-paste of mapping JSON blocks.
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
- all asset mappings failed
- assetPath is not an image referenced by the document
- assetPath is required for analyze
- at least one asset path or mapping is required
- binary files cannot be edited as text
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/30034b21850f9617.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/asset_relink_batch.go:83
p := &assetRelinkPlan{ctx: ctx, batch: batch, lookup: lookup, assetRelinker: &assetRelinker{
collectOnly: true, routes: map[string]*assetRelinker{},
result: apicontract.AssetReferencesData{References: []apicontract.AssetReference{}, SkippedNotebooks: []string{}, DryRun: dryRun},
}}
if len(mappings) == 0 {
return p, errors.New("at least one asset path or mapping is required")
}
seen := map[string]bool{}
var validationErr error
for _, mapping := range mappings {
rule, err := newAssetRelinker(mapping.OldPath, mapping.NewPath)
if !lookup && mapping.NewPath == "" {
err = errors.New("newPath is required")
}
item := &assetRelinkItem{input: mapping, rule: rule}
p.items = append(p.items, item)
if rule.oldPath != "" {
if seen[rule.oldPath] {
validationErr = fmt.Errorf("duplicate oldPath: %s", mapping.OldPath)
}
seen[rule.oldPath] = true
}
if err != nil {
item.inputError = true
p.fail(item, err.Error())
continue
}
p.routes[rule.oldPath] = rule
}
if validationErr != nil {
return p, validationErr
}
for _, item := range p.items {
if item.reason == "" && item.rule.newPath != item.rule.oldPath && seen[item.rule.newPath] {
return p, errors.New("chained or cyclic asset mappings are not supported")
}
}View on GitHub (pinned to 9f775e8a12)