siyuan-note/siyuan · error
invalid history source path
Error message
invalid history source path
What it means
apply() snapshots a source asset into the history directory before relinking. After re-reading the file and confirming it is unchanged, it computes the file's path relative to the workspace data directory; if that relative path cannot be computed or escapes the data directory (a ".." prefix), it refuses to write history outside the workspace and throws this error.
Solutions
- Ensure all asset file paths passed into the relink batch are real paths under util.DataDir (use filepath.EvalSymlinks and relativize before queueing)
- Verify the process is using the same DataDir as when the file list was built
- Re-scan assets after moving or renaming the workspace so stale paths are refreshed
Example fix
// before
rel, relErr := filepath.Rel(util.DataDir, file.path) // file.path points outside DataDir
// after
abs, _ := filepath.EvalSymlinks(file.path)
rel, relErr := filepath.Rel(util.DataDir, abs)
if relErr != nil || strings.HasPrefix(rel, "..") { skip the file } Defensive patterns
Strategy: validation
Validate before calling
rel, err := filepath.Rel(util.DataDir, path)
if err != nil || strings.HasPrefix(rel, "..") { /* skip file, do not queue for history */ } Type guard
func inDataDir(p string) bool { rel, err := filepath.Rel(util.DataDir, p); return err == nil && !strings.HasPrefix(rel, "..") } Prevention
- Always resolve symlinks with filepath.EvalSymlinks before queueing history files
- Rebuild the file list from the current DataDir instead of caching absolute paths
- Log skipped out-of-tree paths during scan for early detection
When it happens
Trigger: Calling run() (or the test TestAssetRelinkSourceChangedDuringScan) with an asset file whose path is not inside util.DataDir, e.g. a symlinked or absolute path that resolves outside the data directory, or when filepath.Rel fails due to mismatched path bases.
Common situations: Workspace moved or data directory relocated so cached file paths point outside it; symlinked assets pointing to external locations; running the relink batch with paths collected from a different workspace.
Understand the failure class
Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.
Related errors
- invalid skill path
- path contains invalid character
- resource escapes the data directory
- access to private/internal IP is prohibited
- access to sensitive workspace file is forbidden
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/e2f3158133d3c9ed.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/asset_relink_batch.go:461
if err != nil {
return err
}
p.result.HistoryPath = filepath.ToSlash(historyDir)
// 所有候选源先备份;备份失败属于请求级错误,此时尚未修改任何引用。
for _, file := range files {
if err = p.checkContext(); err != nil {
return err
}
if file.before == nil {
continue
}
current, readErr := filelock.ReadFile(file.path)
if readErr != nil || !bytes.Equal(current, file.before) {
return fmt.Errorf("source changed during scan: %s", file.path)
}
rel, relErr := filepath.Rel(util.DataDir, file.path)
if relErr != nil || strings.HasPrefix(rel, "..") {
return errors.New("invalid history source path")
}
dest := filepath.Join(historyDir, rel)
if err = os.MkdirAll(filepath.Dir(dest), 0755); err != nil {
return err
}
if err = gulu.File.WriteFileSafer(dest, file.before, 0644); err != nil {
return err
}
}
indexHistoryDir(filepath.Base(historyDir), util.NewLute())
changedViews, reload := map[string]bool{}, map[string]bool{}
defer func() {
for _, tree := range p.trees {
viewChanged := false
ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
if entering && n.Type == ast.NodeAttributeView && changedViews[n.AttributeViewID] {
viewChanged = true
}View on GitHub (pinned to 9f775e8a12)