siyuan-note/siyuan · error
history path [%s] not exist
Error message
history path [%s] not exist
What it means
validateHistoryPath checks that the resolved history path actually exists on disk after confirming it is inside the workspace. If gulu.File.IsExist reports the file is absent, the caller asked to operate on a history entry that no longer exists.
Source
Thrown at kernel/model/history.go:593
if err = filelock.CopyNewtimes(from, to); err != nil {
logging.LogErrorf("copy file [%s] to [%s] failed: %s", from, to, err)
return
}
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)View on GitHub (pinned to 8641553a1f)
Solutions
- Re-query the history list (/api/history/getHistoryItems) and use a currently existing path
- Check retention settings (Settings - History) — the entry may have been auto-pruned; raise the retention period if needed
- Verify the file exists in data/history before calling the API
- Refresh the history panel so the UI list matches disk state
Example fix
// before: using a cached path that was pruned
rollbackDocHistory(cachedHistoryPath)
// after: re-list and pick a live entry
const items = await fetchPost("/api/history/getHistoryItems", {query: ""});
rollbackDocHistory(items[0].path) Defensive patterns
Strategy: validation
Validate before calling
async function historyPathExists(historyPath) {
const r = await fetchPost("/api/filetree/checkFileExist", {path: historyPath});
return r.code === 0 && r.data.isExist === true;
} Type guard
null
Try / catch
try { await rollbackDocHistory(p); } catch (e) { if (String(e.msg).includes("not exist")) { await refreshHistoryList(); /* pick a live entry */ } else { throw e; } } Prevention
- Refresh the history list before each rollback instead of caching paths
- Be aware the history retention job prunes old snapshots automatically
- Re-query history after sync operations from other devices
When it happens
Trigger: Calling any history API (GetDocHistoryContent, RollbackDocHistory, RollbackAssetsHistory, RollbackNotebookHistory, RollbackAttributeViewHistory, ResolveDocVersionBoxID) with a path whose file was already removed — typically by history auto-cleanup (retention limits), manual deletion of data/history, or a typo in the path.
Common situations: UI kept an open history list after the retention job pruned old entries; another client (sync/second device) deleted history meanwhile; stale book/bookmark referencing pruned snapshots; hard-coded paths in tests or scripts.
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
- encrypted attribute view history is missing notebook context
- Content block with id [%s] not found
- marketplace package manifest not found
- --path is required
- CLI does not support encrypted notebook history
AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11).
Data as JSON: /api/errors/57421fc21b3d3113.
Report an issue: GitHub.