siyuan-note/siyuan · error
Query asset failed [ ]
Error message
Query asset failed [%s]
What it means
GetAssetAbsPathInBox could not find the asset under data/<boxID>/ and the box is encrypted, so the fallback to the global data/assets/ search is skipped - encrypted notebooks are isolation islands whose assets never cross boundaries. The localized 'Query asset failed [<path>]' (Conf.Language(12)) is returned.
Solutions
- Unlock the notebook (Settings - Security) and verify the file exists under data/<boxID>/assets/
- Restore the missing file from data/history (File - History) or a sync snapshot
- Re-insert the asset into the document so a fresh copy lands in the box's assets folder
- If the asset is intentionally global, reference it without the box query parameter
Defensive patterns
Strategy: validation
Validate before calling
if model.IsEncryptedBox(boxID) && !filelock.IsExist(filepath.Join(util.DataDir, boxID, relPath)) {
// unresolvable in an encrypted box: offer restore from history instead of calling the API
} Try / catch
A non-nil error here means the asset is genuinely unresolvable in that encrypted box: catch it and render a missing-asset placeholder in the UI rather than retrying or falling back to arbitrary disk locations.
Prevention
- Do not delete or rename files inside an encrypted notebook's assets folder from the file manager
- Use in-app clean/rename operations so references stay consistent
- Keep history and sync enabled for encrypted notebooks so removals are recoverable
When it happens
Trigger: Resolving an asset path whose file is missing from an encrypted notebook's assets folder: the file was deleted or moved on disk, the document references a stale path, or the asset actually lives in global data/assets but an encrypted box never falls back to it.
Common situations: Manual file cleanup inside data/<boxID>/assets/; sync conflicts that removed files; assets renamed externally so documents keep old paths.
Related errors
AI-assisted analysis of siyuan-note/siyuan@afa823b6b4 (2026-08-18).
Data as JSON: /api/errors/8c51a3f737a8ffb5.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/assets.go:1155
if !gulu.File.IsSubPath(util.WorkspaceDir, realP) {
return "", fmt.Errorf("symlink [%s] resolves outside workspace: [%s]", p, realP)
}
// 验证解析后的路径仍在 <boxID>/assets/ 或全局 data/assets/ 下
expectedPrefix := filepath.Join(util.DataDir, "assets")
if boxID != "" {
expectedPrefix = filepath.Join(util.DataDir, boxID, "assets")
}
if !gulu.File.IsSubPath(expectedPrefix, realP) {
return "", fmt.Errorf("symlink [%s] resolves outside assets directory: [%s]", p, realP)
}
}
return p, nil
}
// 非加密 box 的资源可能回退到全局 data/assets(兼容旧笔记本结构)
if !IsEncryptedBox(boxID) {
return GetAssetAbsPathWithOpt(relativePath, false)
}
return "", fmt.Errorf(Conf.Language(12), relativePath)
}
// GetAssetAbsPathWithOpt 与 GetAssetAbsPath 一致,但可通过 includeEncrypted 控制是否遍历加密 box。
// serveAssets 传 true(下游 serveEncryptedAsset 会按锁定状态 fail-closed),其他调用方传 false(安全跳过)。
func GetAssetAbsPathWithOpt(relativePath string, includeEncrypted bool) (string, error) {
relativePath = strings.TrimSpace(relativePath)
if idx := strings.Index(relativePath, "?"); idx >= 0 {
relativePath = relativePath[:idx]
}
absPath, err := getAssetAbsPath(relativePath, includeEncrypted)
if err == nil && absPath != "" {
return absPath, nil
}
if err != nil {
return "", err
}View on GitHub (pinned to afa823b6b4)