siyuan-note/siyuan · error

box and dataPath cannot be used together

Error message

box and dataPath cannot be used together

What it means

resolveAssetRequestPath serves /assets/ requests in two mutually exclusive ways: by box ID (resolve within a notebook) or by an explicit data path (resolve relative to the workspace data directory). Passing both at once is ambiguous, so the handler rejects the request outright before doing any file resolution.

Source

Thrown at kernel/server/serve.go:986

		}
		return gulu.File.IsSubPath(filepath.Join(util.DataDir, requestBoxID, "assets"), assetAbsPath)
	}

	if model.IsEncryptedAssetPath(assetAbsPath) {
		return false
	}
	dataRelativePath, err := filepath.Rel(util.DataDir, assetAbsPath)
	if err != nil {
		return false
	}
	_, validatedAbsPath, err := model.ResolveDataAssetPath(filepath.ToSlash(dataRelativePath))
	return err == nil && filepath.Clean(validatedAbsPath) == filepath.Clean(assetAbsPath)
}

func resolveAssetRequestPath(cleanPath, boxID, dataPath string) (string, error) {
	if dataPath != "" {
		if boxID != "" {
			return "", errors.New("box and dataPath cannot be used together")
		}
		dataRelativePath, assetAbsPath, err := model.ResolveDataAssetPath(dataPath)
		if err != nil {
			return "", err
		}
		assetPath, _, ok := model.AssetPathFromDataRelativePath(dataRelativePath)
		if !ok || assetPath != cleanPath {
			return "", fmt.Errorf("asset path [%s] does not match data path [%s]", cleanPath, dataPath)
		}
		return assetAbsPath, nil
	}
	if boxID != "" {
		return model.GetAssetAbsPathInBox(cleanPath, boxID)
	}
	return model.GetAssetAbsPath(cleanPath)
}

func serveAssets(ginServer *gin.Engine) {

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Remove the `box` query parameter from the request URL and keep only `dataPath`
  2. Or remove `dataPath` and keep only `box` when the asset lives inside a specific notebook
  3. Fix the client code that constructs the URL so it chooses exactly one resolution strategy

Example fix

// before
GET /assets/foo.png?box=20240101120000-abc123&dataPath=assets/foo.png
// after
GET /assets/foo.png?box=20240101120000-abc123
Defensive patterns

Strategy: validation

Validate before calling

// build the asset URL with exactly one resolution strategy
const url = dataPath
  ? `/assets/${name}?dataPath=${encodeURIComponent(dataPath)}`
  : `/assets/${name}?box=${boxID}`;

Try / catch

try {
  const res = await fetch(url);
  if (res.status === 400 && (await res.text()).includes("box and dataPath")) {
    // strip one param and retry
  }
} catch (e) { /* network */ }

Prevention

When it happens

Trigger: An HTTP request to the asset endpoint that supplies both the `box` query parameter and the `dataPath` query parameter; any code path calling resolveAssetRequestPath(cleanPath, boxID, dataPath) with both non-empty strings (kernel/server/serve.go:986).

Common situations: A client (script, plugin, third-party WebDAV/HTTP client) built an asset URL by blindly appending every known parameter; a bookmarked URL from an older SiYuan version that used box now also carries dataPath after a frontend change.

Related errors


AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11). Data as JSON: /api/errors/5207dcbac3fa9695. Report an issue: GitHub.