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

  1. Re-query the history list (/api/history/getHistoryItems) and use a currently existing path
  2. Check retention settings (Settings - History) — the entry may have been auto-pruned; raise the retention period if needed
  3. Verify the file exists in data/history before calling the API
  4. 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

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


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