siyuan-note/siyuan · error

history path is missing notebook context [%s]

Error message

history path is missing notebook context [%s]

What it means

RollbackDocHistory expects history paths shaped <historyDir>/<datePrefix>/<boxID>/<relativePath>. After stripping the history directory prefix, the path must still contain at least a date segment, a notebook ID segment, and a remainder. If splitting on '/' yields fewer than 3 parts, the path does not carry notebook context and rollback cannot determine the target notebook, so it fails.

Source

Thrown at kernel/model/history.go:288

	}
	return
}

func RollbackDocHistory(historyPath string) (err error) {
	historyPath, err = validateHistoryPath(historyPath)
	if err != nil {
		return
	}

	FlushTxQueue()

	relPath := strings.TrimPrefix(historyPath, util.HistoryDir)
	relPath = strings.TrimPrefix(relPath, string(os.PathSeparator))
	relPath = filepath.ToSlash(relPath)
	parts := strings.SplitN(relPath, "/", 3)
	if len(parts) < 3 {
		logging.LogWarnf("invalid history path [%s]", historyPath)
		return fmt.Errorf("history path is missing notebook context [%s]", historyPath)
	}
	boxID := parts[1]
	origBoxID := boxID // 保留原始 boxID 用于解密(getRollbackBox 可能返回不同的 box)
	encrypted := IsEncryptedBox(origBoxID)
	if encrypted {
		// 整个回滚持有操作租约,内部文件读写可独立获取读锁,锁定等待租约结束后再申请写锁。
		if err = AcquireEncryptedBoxOperation(origBoxID); err != nil {
			return
		}
		defer ReleaseEncryptedBoxOperation(origBoxID)
	}

	// 加密笔记本的历史回滚要求原笔记本已挂载:
	// WriteTree 根据 tree.Box 判断是否加密落盘。若原笔记本未挂载导致
	// getRollbackBox fallback 到普通 Rollback 笔记本,解密后的 .sy 将被 WriteTree
	// 以明文落盘,违反"WriteTree auto-encrypts on write-back"的设计承诺。
	if IsEncryptedBox(origBoxID) && nil == Conf.Box(origBoxID) {
		logging.LogErrorf("rollback encrypted doc history requires notebook [%s] to be mounted", origBoxID)

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Pass the full path including the notebook ID segment: data/history/<date>/<boxID>/<path>
  2. Use the historyPath exactly as returned by the history listing API instead of constructing it manually
  3. For non-document history (assets/notebooks), use the corresponding rollback APIs (rollbackAssetHistory etc.)

Example fix

// before
path := filepath.Join(util.HistoryDir, "20260910", "20260910200000-abc.sy")
// after
path := filepath.Join(util.HistoryDir, "20260910", "20240910", "20260910200000-abc.sy")
Defensive patterns

Strategy: validation

Validate before calling

const parts = historyPath.replace(/^.*?history\//, "").split("/")
if (parts.length < 3) throw new Error("historyPath must be <date>/<boxID>/<path>; got " + historyPath)

Type guard

function hasNotebookContext(historyPath) {
  const m = historyPath.match(/history\/[\w-]+\/[^/]+\/.+/)
  return m !== null
}

Try / catch

try {
  await fetchPost("/api/history/rollbackDocHistory", {historyPath})
} catch (e) {
  if (String(e.msg || e).includes("missing notebook context")) {
    // rebuild path from the history listing API response instead
  }
}

Prevention

When it happens

Trigger: Calling rollbackDocHistory (kernel API history/rollbackDocHistory) with a historyPath that is directly under util.HistoryDir or has only a date prefix (e.g. data/history/20260910/doc.sy) instead of data/history/20260910/<boxID>/doc.sy.

Common situations: Passing an asset or notebook-level history path to the document rollback API; history directory layout changed between SiYuan versions; constructing the path manually from the history list without the boxID segment.

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


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