{"record":{"id":"c787debc4edcd52f","repo":"siyuan-note/siyuan","slug":"invalid-notebook-history-path-s","errorCode":null,"errorMessage":"invalid notebook history path [%s]","messagePattern":"invalid notebook history path \\[(.+?)\\]","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/history.go","lineNumber":661,"sourceCode":"\tif filelock.IsExist(to) {\n\t\treturn errors.New(Conf.Language(371))\n\t}\n\n\tif err = filelock.CopyNewtimes(from, to); err != nil {\n\t\tlogging.LogErrorf(\"copy file [%s] to [%s] failed: %s\", from, to, err)\n\t\treturn\n\t}\n\n\tIncSync()\n\tReloadFiletree()\n\tutil.PushMsg(Conf.Language(372), 3000)\n\treturn nil\n}\n\nfunc validateNotebookHistoryPath(historyPath string) (boxID string, err error) {\n\trel, err := filepath.Rel(util.HistoryDir, historyPath)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"invalid notebook history path [%s]\", historyPath)\n\t}\n\tparts := strings.Split(filepath.ToSlash(rel), \"/\")\n\tif len(parts) != 2 || !strings.HasSuffix(parts[0], \"-\"+HistoryOpDelete) || !ast.IsNodeIDPattern(parts[1]) ||\n\t\t!gulu.File.IsDir(historyPath) || !filelock.IsExist(filepath.Join(historyPath, \".siyuan\", \"conf.json\")) {\n\t\treturn \"\", fmt.Errorf(\"invalid notebook history path [%s]\", historyPath)\n\t}\n\treturn parts[1], nil\n}\n\nfunc RollbackAttributeViewHistory(historyPath string) (err error) {\n\thistoryPath, err = validateHistoryPath(historyPath)\n\tif err != nil {\n\t\treturn\n\t}\n\t// 验证目标文件必须是 AV 定义文件\n\tif !strings.HasSuffix(historyPath, \".json\") || !strings.Contains(filepath.ToSlash(historyPath), \"/storage/av/\") {\n\t\treturn fmt.Errorf(\"invalid AV history path [%s]\", historyPath)\n\t}","sourceCodeStart":643,"sourceCodeEnd":679,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/history.go#L643-L679","documentation":"validateNotebookHistoryPath only accepts paths that are exactly two segments relative to data/history: '<timestamp>-deleted/<boxID>' where the op suffix is the delete operation, the box ID matches the node-ID pattern, the path is a directory, and it contains .siyuan/conf.json. A Rel computation failure (path not under HistoryDir) produces this error before those structural checks.","triggerScenarios":"Calling RollbackNotebookHistory with a path outside data/history (Rel fails), a doc or asset history path, or a malformed notebook history folder (wrong depth, wrong op suffix, missing .siyuan/conf.json).","commonSituations":"Passing a document history path to the notebook rollback API by mistake; manually copied notebook history missing the .siyuan directory; history from a custom backup tool laid out differently; API misuse mixing rollback endpoints.","solutions":["Pass a path of the form data/history/<timestamp>-deleted/<boxID> taken directly from the notebook-history listing API","Ensure the history directory contains .siyuan/conf.json — if the copy lost it, restore the full snapshot including .siyuan","Use /api/history/rollbackDocHistory or rollbackAssetsHistory for non-notebook histories, not rollbackNotebookHistory"],"exampleFix":"// before: doc history path given to notebook rollback\nrollbackNotebookHistory(\"history/20240101120000-update/<boxID>/<docID>.sy\")\n// after: proper deleted-notebook history path\nrollbackNotebookHistory(\"history/20240101120000-deleted/<boxID>\")","handlingStrategy":"validation","validationCode":"function looksLikeNotebookHistory(historyPath) {\n  const parts = historyPath.replace(/\\\\/g, \"/\").split(\"/\");\n  return parts.length === 2 && parts[0].endsWith(\"-deleted\") && /^[0-9a-z]{20}$/.test(parts[1]);\n}","typeGuard":"null","tryCatchPattern":"try { await rollbackNotebookHistory(p); } catch (e) { if (String(e.msg).startsWith(\"invalid notebook history path\")) { /* route to the correct rollback API by path shape */ } else { throw e; } }","preventionTips":["Pick the rollback endpoint based on the history item type from the listing API","Keep the .siyuan directory intact when copying history snapshots","Use API-returned paths, not hand-assembled ones"],"tags":["history","path-validation","notebook"],"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-14T05:17:10.506Z"}