siyuan-note/siyuan · error
chained or cyclic asset mappings are not supported
Error message
chained or cyclic asset mappings are not supported
What it means
Batch mappings must be independent: a mapping's NewPath may not itself be the OldPath of another mapping in the same batch, since that would create a chain (a->b, b->c) or a cycle, requiring ordered application the planner does not support. The constructor scans for this condition and rejects the whole batch.
Solutions
- Split chained renames into separate sequential batches, waiting for each batch to complete before submitting the next
- Rewrite the chain so each mapping's new path is a final destination not used as any source in the same batch
- Detect chains client-side by building a graph of oldPath->newPath and checking that no newPath is a source key
Example fix
// before
mappings := [{a.png,b.png},{b.png,c.png}] // chained: rejected
model.RelinkAssets(ctx, mappings, false)
// after
model.RelinkAssets(ctx, [{b.png,c.png}], false) // batch 1
model.RelinkAssets(ctx, [{a.png,b.png}], false) // batch 2 after batch 1 done Defensive patterns
Strategy: validation
Validate before calling
srcs := map[string]bool{}
for _, m := range mappings { srcs[m.OldPath] = true }
for _, m := range mappings {
if m.NewPath != m.OldPath && srcs[m.NewPath] {
return errors.New("chained mapping detected: " + m.NewPath)
}
} Prevention
- Reject chains in the mapping builder UI
- Run cascading renames as sequential batches
- Model mappings as a graph and assert no newPath is also a source
When it happens
Trigger: Calling RelinkAssets with mappings where some NewPath equals another mapping's OldPath, e.g. [{old:a.png,new:b.png},{old:b.png,new:c.png}] or a cycle [{a->b},{b->a}].
Common situations: Renaming a sequence of assets by shifting names (a->b, b->c); scripted rotations of file names; refactor tools generating cascading rename plans.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 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/62dec643750e05ff.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/asset_relink_batch.go:99
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")
}
}
return p, nil
}
func runAssetRelinks(ctx context.Context, mappings []apicontract.AssetRelinkMapping, dryRun, lookup, batch bool) (apicontract.AssetReferencesData, error) {
p, err := newAssetRelinkPlan(ctx, mappings, dryRun, lookup, batch)
if err != nil {
p.failAll(err)
return p.response(err)
}
var lastProgress time.Time
p.progress = func(path string) {
if Conf != nil && time.Since(lastProgress) >= 250*time.Millisecond {
lastProgress = time.Now()
language := 70
if p.saving {
language = 111View on GitHub (pinned to 9f775e8a12)