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

  1. Split chained renames into separate sequential batches, waiting for each batch to complete before submitting the next
  2. Rewrite the chain so each mapping's new path is a final destination not used as any source in the same batch
  3. 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

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


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 = 111

View on GitHub (pinned to 9f775e8a12)