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

  1. Unlock the notebook (Settings - Security) and verify the file exists under data/<boxID>/assets/
  2. Restore the missing file from data/history (File - History) or a sync snapshot
  3. Re-insert the asset into the document so a fresh copy lands in the box's assets folder
  4. 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

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)