siyuan-note/siyuan · error
ambiguous asset path
Error message
ambiguous asset path: %s
What it means
During asset relink preflight, resolveRelinkAsset checks each notebook's assets/ root for the referenced asset. If the same asset-relative path resolves to an existing regular file under more than one root and the candidates differ, the path is ambiguous — the relinker cannot know which file the reference means, so it aborts rather than silently relinking to the wrong file.
Solutions
- Rename the asset in one notebook so the relative path is unique per root
- Delete the duplicate copy that is not actually referenced and update references to point at the surviving asset
- Relink with a single asset root so the path can only resolve once
- If intentional sharing is needed, keep one canonical copy under one root and reference it via that root only
Example fix
// before (two roots both contain assets/foo.png)
resolveRelinkAsset([]string{nb1Assets, nb2Assets}, "assets/foo.png", true)
// error: ambiguous asset path: assets/foo.png
// after (rename one copy so each root has a unique path)
resolveRelinkAsset([]string{nb1Assets, nb2Assets}, "assets/foo.png", true) // nb1/assets/foo.png -> nb1/assets/foo-1.png Defensive patterns
Strategy: validation
Validate before calling
// before relinking, ensure each referenced asset path resolves to exactly one root
func isUnambiguous(roots []string, assetPath string) bool {
count := 0
for _, root := range roots {
if _, err := os.Stat(filepath.Join(root, strings.TrimPrefix(assetPath, "assets/"))); err == nil {
count++
}
}
return count <= 1
} Prevention
- Keep asset filenames unique across notebooks
- Avoid copying the same asset into multiple assets/ folders; reference one canonical copy
- Audit assets/ directories for duplicate filenames before running relink
When it happens
Trigger: An asset reference like 'assets/foo.png' exists in two different notebook asset roots (e.g. notebook1/assets/foo.png and notebook2/assets/foo.png), or in a notebook assets dir plus a shared/synced assets root, and resolveRelinkAsset is invoked with multiple roots by scanMetadata during a relink scan.
Common situations: The same file was copied into two notebooks' assets folders; synced workspaces merged duplicate assets; a shared asset root was added alongside notebook-local assets so both resolve the same relative path.
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
- target asset does not exist locally
- accessing assets in encrypted notebook
- asset path must be a file
- Conf.Language(0)
- Conf.Language(0) (localized open notebook failure message)
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/c0ebcdca3a8c976b.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/asset_relink.go:269
if !info.Mode().IsRegular() {
return "", fmt.Errorf("asset must be a regular file: %s", assetPath)
}
real, err := filepath.EvalSymlinks(candidate)
if err != nil {
return "", err
}
realRoot, err := filepath.EvalSymlinks(root)
if err != nil || !gulu.File.IsSubPath(realRoot, real) {
return "", fmt.Errorf("asset escapes its directory: %s", assetPath)
}
if err = validateRelinkStoragePath(real); err != nil {
return "", err
}
if IsEncryptedAssetPath(real) {
return "", errors.New("encrypted assets are not supported")
}
if found != "" && found != candidate {
return "", fmt.Errorf("ambiguous asset path: %s", assetPath)
}
found = candidate
}
if required && found == "" {
return "", fmt.Errorf("target asset does not exist locally: %s", assetPath)
}
return found, nil
}
func (p *assetRelinkPlan) scanViews() error {
dir := filepath.Join(util.DataDir, "storage", "av")
if err := p.observe(dir); err != nil {
return err
}
entries, err := os.ReadDir(dir)
if err != nil && !os.IsNotExist(err) {
return err
}View on GitHub (pinned to 9f775e8a12)