siyuan-note/siyuan · error
history path [%s] is not under history directory
Error message
history path [%s] is not under history directory
What it means
After confirming the path is inside the workspace and exists, validateHistoryPath computes its path relative to util.HistoryDir and rejects it if the relative path escapes the history directory (starts with '..') or Rel fails. Only files genuinely under data/history may be used as history paths, preventing misuse of arbitrary workspace files as history sources.
Source
Thrown at kernel/model/history.go:597
}
IncSync()
util.PushMsg(Conf.Language(102), 3000)
return nil
}
// validateHistoryPath 校验历史路径是否位于工作区内且属于历史目录。
// 拒绝路径穿越攻击(..、绝对路径等)。返回规范化的绝对路径。
func validateHistoryPath(historyPath string) (string, error) {
p := filepath.Join(util.WorkspaceDir, historyPath)
if !gulu.File.IsSubPath(util.WorkspaceDir, p) {
return "", fmt.Errorf("history path [%s] is not in workspace", historyPath)
}
if !gulu.File.IsExist(p) {
return "", fmt.Errorf("history path [%s] not exist", historyPath)
}
rel, err := filepath.Rel(util.HistoryDir, p)
if err != nil || strings.HasPrefix(rel, "..") {
return "", fmt.Errorf("history path [%s] is not under history directory", historyPath)
}
return p, nil
}
// IsEncryptedHistoryPath 判断历史路径是否明确属于加密笔记本。
func IsEncryptedHistoryPath(absPath string) bool {
boxID := ExtractBoxIDFromHistoryPath(absPath)
if boxID == "" {
return false
}
if IsEncryptedBox(boxID) {
return true
}
rel, err := filepath.Rel(util.HistoryDir, absPath)
if err != nil {
return false
}
parts := strings.SplitN(filepath.ToSlash(rel), "/", 3)View on GitHub (pinned to 8641553a1f)
Solutions
- Use only paths whose prefix is the workspace data/history directory as returned by history listing APIs
- Do not substitute live-file paths for history paths; locate the corresponding snapshot under data/history
- Rebuild/refresh the history index if the expected snapshot path resolves elsewhere
Example fix
// before: live path, not a history snapshot
rollbackAttributeViewHistory("data/storage/av/view.json")
// after: history snapshot under data/history
rollbackAttributeViewHistory("data/history/20240101120000-update/<boxID>/storage/av/view.json") Defensive patterns
Strategy: validation
Validate before calling
function isUnderHistoryDir(historyPath) {
const normalized = historyPath.replace(/\\/g, "/");
return normalized.startsWith("history/") || normalized.includes("/history/");
} Type guard
null
Try / catch
try { await rollbackDocHistory(p); } catch (e) { if (String(e.msg).includes("not under history directory")) { /* switch to the appropriate live-file API or locate the snapshot */ } else { throw e; } } Prevention
- Remember history APIs only accept data/history/... paths
- Use separate live-file APIs for working documents and assets
- Match each history item type to its dedicated rollback endpoint
When it happens
Trigger: Passing a workspace path that exists but is not under data/history — e.g. data/documents, data/assets, data/storage/av files directly, or a path resolving via symlink outside the history tree, to any of the history APIs.
Common situations: Confusing the live document path with its history snapshot; passing a storage/av live JSON instead of its history copy; scripting rollbacks with workspace-relative doc paths; attempting to 'restore' an asset by pointing at the live assets directory.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- invalid archive entry path [%s]
- archive entry resolves outside destination [%s]
- marketplace package contains an invalid path
- asset path is sensitive: %s
- asset path must be relative to data directory: %s
AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11).
Data as JSON: /api/errors/ba18f766b1825196.
Report an issue: GitHub.