{"record":{"id":"8ce4544c17263c50","repo":"siyuan-note/siyuan","slug":"history-path-is-missing-notebook-context-s","errorCode":null,"errorMessage":"history path is missing notebook context [%s]","messagePattern":"history path is missing notebook context \\[(.+?)\\]","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/history.go","lineNumber":288,"sourceCode":"\t}\n\treturn\n}\n\nfunc RollbackDocHistory(historyPath string) (err error) {\n\thistoryPath, err = validateHistoryPath(historyPath)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tFlushTxQueue()\n\n\trelPath := strings.TrimPrefix(historyPath, util.HistoryDir)\n\trelPath = strings.TrimPrefix(relPath, string(os.PathSeparator))\n\trelPath = filepath.ToSlash(relPath)\n\tparts := strings.SplitN(relPath, \"/\", 3)\n\tif len(parts) < 3 {\n\t\tlogging.LogWarnf(\"invalid history path [%s]\", historyPath)\n\t\treturn fmt.Errorf(\"history path is missing notebook context [%s]\", historyPath)\n\t}\n\tboxID := parts[1]\n\torigBoxID := boxID // 保留原始 boxID 用于解密（getRollbackBox 可能返回不同的 box）\n\tencrypted := IsEncryptedBox(origBoxID)\n\tif encrypted {\n\t\t// 整个回滚持有操作租约，内部文件读写可独立获取读锁，锁定等待租约结束后再申请写锁。\n\t\tif err = AcquireEncryptedBoxOperation(origBoxID); err != nil {\n\t\t\treturn\n\t\t}\n\t\tdefer ReleaseEncryptedBoxOperation(origBoxID)\n\t}\n\n\t// 加密笔记本的历史回滚要求原笔记本已挂载：\n\t// WriteTree 根据 tree.Box 判断是否加密落盘。若原笔记本未挂载导致\n\t// getRollbackBox fallback 到普通 Rollback 笔记本，解密后的 .sy 将被 WriteTree\n\t// 以明文落盘，违反\"WriteTree auto-encrypts on write-back\"的设计承诺。\n\tif IsEncryptedBox(origBoxID) && nil == Conf.Box(origBoxID) {\n\t\tlogging.LogErrorf(\"rollback encrypted doc history requires notebook [%s] to be mounted\", origBoxID)","sourceCodeStart":270,"sourceCodeEnd":306,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/history.go#L270-L306","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Pass the full path including the notebook ID segment: data/history/<date>/<boxID>/<path>","Use the historyPath exactly as returned by the history listing API instead of constructing it manually","For non-document history (assets/notebooks), use the corresponding rollback APIs (rollbackAssetHistory etc.)"],"exampleFix":"// before\npath := filepath.Join(util.HistoryDir, \"20260910\", \"20260910200000-abc.sy\")\n// after\npath := filepath.Join(util.HistoryDir, \"20260910\", \"20240910\", \"20260910200000-abc.sy\")","handlingStrategy":"validation","validationCode":"const parts = historyPath.replace(/^.*?history\\//, \"\").split(\"/\")\nif (parts.length < 3) throw new Error(\"historyPath must be <date>/<boxID>/<path>; got \" + historyPath)","typeGuard":"function hasNotebookContext(historyPath) {\n  const m = historyPath.match(/history\\/[\\w-]+\\/[^/]+\\/.+/)\n  return m !== null\n}","tryCatchPattern":"try {\n  await fetchPost(\"/api/history/rollbackDocHistory\", {historyPath})\n} catch (e) {\n  if (String(e.msg || e).includes(\"missing notebook context\")) {\n    // rebuild path from the history listing API response instead\n  }\n}","preventionTips":["Always take historyPath verbatim from the history listing API output","Do not construct history paths by hand; keep the <date>/<boxID>/<path> layout","Use the correct rollback API per history type (doc vs asset vs notebook)"],"tags":["go","history","path-format"],"backgroundTag":"invalid-argument-format","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}