siyuan-note/siyuan · error
target asset does not exist locally
Error message
target asset does not exist locally: %s
What it means
resolveRelinkAsset requires that a referenced asset exists locally when the required flag is set. If none of the asset roots contain a regular file matching the reference path, the relink scan fails because there is no local file to relink to or to verify.
Solutions
- Place the missing asset file at the referenced path under one of the asset roots
- Re-sync the workspace so the referenced asset is downloaded to this device
- Fix the reference path in the document if it points to a wrong/renamed asset
- Remove the stale reference if the asset is no longer needed
Example fix
// before
// document references assets/old-name.png, file missing locally
// after
// copy the file into place:
os.Rename("downloads/old-name.png", filepath.Join(nbAssets, "old-name.png")) Defensive patterns
Strategy: validation
Validate before calling
// verify the asset exists locally before triggering a relink
func assetExists(roots []string, assetPath string) bool {
for _, root := range roots {
if _, err := os.Stat(filepath.Join(root, strings.TrimPrefix(assetPath, "assets/"))); err == nil {
return true
}
}
return false
} Try / catch
// wrap the relink scan to surface missing-asset errors
if err := plan.Scan(); err != nil {
if strings.Contains(err.Error(), "target asset does not exist locally") {
// fetch/sync the asset, then retry
}
return err
} Prevention
- Fully sync workspaces before relinking so all referenced assets exist locally
- Run asset download for referenced assets on devices with on-demand download enabled
- Fix or remove broken asset references when documents are moved between notebooks
When it happens
Trigger: A document references 'assets/missing.png' but no notebook asset root contains assets/missing.png; scanMetadata calls resolveRelinkAsset with required=true during relink preflight.
Common situations: Assets were deleted or not synced to this device; the workspace was restored from a partial backup; on-demand asset download has not fetched the file yet; the reference was hand-edited to a wrong path.
Understand the failure class
Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.
Related errors
- ambiguous asset path
- 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/94c3330fa929c49d.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/asset_relink.go:274
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
}
for _, entry := range entries {
if err := p.checkContext(); err != nil {
return err
}
id := strings.TrimSuffix(entry.Name(), ".json")View on GitHub (pinned to 9f775e8a12)