siyuan-note/siyuan · error

newPath is required

Error message

newPath is required

What it means

When performing an actual relink (lookup mode is false), every mapping must carry a NewPath: a relink defines where the old asset should move. A mapping whose NewPath is empty is recorded as an inputError on the item and reported with this message; in lookup mode (FindAssetReferences*) empty NewPath is allowed.

Solutions

  1. Fill in NewPath for every mapping before calling the relink functions
  2. Use the lookup APIs (FindAssetReferences*/lookup mode) when you only want references, not a move
  3. Validate the payload client-side so rows without newPath are rejected before submission

Example fix

// before
mappings := []apicontract.AssetRelinkMapping{{OldPath: "assets/a.png"}}
model.RelinkAssets(ctx, mappings, false)
// after
mappings := []apicontract.AssetRelinkMapping{{OldPath: "assets/a.png", NewPath: "assets/b.png"}}
for _, m := range mappings {
	if m.NewPath == "" { return errors.New("newPath required for " + m.OldPath) }
}
model.RelinkAssets(ctx, mappings, false)
Defensive patterns

Strategy: validation

Validate before calling

for _, m := range mappings {
	if !lookup && m.NewPath == "" { return fmt.Errorf("newPath required for %q", m.OldPath) }
}

Type guard

if !lookup && mapping.NewPath == "" { return false }

Prevention

When it happens

Trigger: Calling RelinkAssetWithContext or RelinkAssets (dryRun true or false) with any mapping whose NewPath field is an empty string; lookup calls (FindAssetReferencesBatch) are exempt.

Common situations: Client built request for the find/references endpoint but sent it to the relink endpoint; JSON payload omitted newPath for some entries; batch UI left a row half-filled.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/491475ae16f848f4. Report an issue: GitHub.

Appendix: source

Thrown at kernel/model/asset_relink_batch.go:77

func RelinkAssets(ctx context.Context, mappings []apicontract.AssetRelinkMapping, dryRun bool) (apicontract.AssetReferencesData, error) {
	return runAssetRelinks(ctx, mappings, dryRun, false, true)
}

func newAssetRelinkPlan(ctx context.Context, mappings []apicontract.AssetRelinkMapping, dryRun, lookup, batch bool) (*assetRelinkPlan, error) {
	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

View on GitHub (pinned to 9f775e8a12)