siyuan-note/siyuan · error
cannot allocate a replacement history directory
Error message
cannot allocate a replacement history directory
What it means
Before applying a relink, the planner creates a timestamped backup directory under the history dir named <time>-<op>. It tries up to 1000 successive seconds for a free name; if every Mkdir collides with an existing directory (or clock anomalies make all names taken), it gives up with this error rather than risk overwriting an existing history entry.
Solutions
- Clean up old/obsolete entries in data/history/ (or archive them elsewhere)
- Correct the system clock (NTP sync) if it is skewed relative to existing history dirs
- Check for existing dirs matching 2006-01-02-150405-replace and remove future-dated collisions
- Retry after freeing/normalizing the history directory
Example fix
// before // data/history contains 2026-09-18-120000-replace ... many future dirs // after mv data/history/2026-09-18-120000-replace /archive/history-backup/ // re-run the relink apply
Defensive patterns
Strategy: retry
Validate before calling
// pre-check history dir for colliding future-dated replace dirs
pattern := time.Now().Format("2006-01-02")
matches, _ := filepath.Glob(filepath.Join(historyDir, pattern+"*-replace"))
if len(matches) > 900 {
// archive/clean old history entries first
} Try / catch
// retry after cleaning the history directory
if err := plan.Apply(); err != nil {
if err.Error() == "cannot allocate a replacement history directory" {
// archive old data/history entries, fix clock, then retry once
}
return err
} Prevention
- Keep the system clock NTP-synced so timestamped history names do not collide with future entries
- Regularly archive/prune data/history to avoid tens of thousands of entries
- Do not manually pre-create directories in data/history
When it happens
Trigger: newAssetRelinkHistoryDir is called from apply; all 1000 candidate timestamp directories already exist in data/history/ — e.g. thousands of replace operations created dirs within the same second-range, or clock skew keeps regenerating identical names that pre-exist.
Common situations: System clock set far ahead so future-timestamped dirs already exist; history dir polluted by many manual/test entries; the same relink applied repeatedly across clock jumps.
Understand the failure class
Background: "already exists" / EEXIST / FileAlreadyExistsException: what the 'file already exists' error means and how to fix it — this error's family across 37 libraries.
Related errors
- create history directory
- get history directory failed
- mkdir notebook crypto backup dir failed
- read document [ ] failed
- read encrypted notebook history conf
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/1dbdeb2706d55c76.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/asset_relink.go:479
}
}
p.result.References = append(p.result.References, ref)
return nil
}
func newAssetRelinkHistoryDir() (string, error) {
if err := os.MkdirAll(util.HistoryDir, 0755); err != nil {
return "", err
}
for offset := 0; offset < 1000; offset++ {
dir := filepath.Join(util.HistoryDir, time.Now().Add(time.Duration(offset)*time.Second).Format("2006-01-02-150405")+"-"+HistoryOpReplace)
if err := os.Mkdir(dir, 0755); err == nil {
return dir, nil
} else if !os.IsExist(err) {
return "", err
}
}
return "", errors.New("cannot allocate a replacement history directory")
}
func validateRelinkStoragePath(abs string) error {
real, err := filepath.EvalSymlinks(abs)
if err != nil {
return err
}
dataRoot, err := filepath.EvalSymlinks(util.DataDir)
if err != nil || !gulu.File.IsSubPath(dataRoot, real) {
return fmt.Errorf("resource escapes the data directory: %s", abs)
}
rel, err := filepath.Rel(dataRoot, real)
if err != nil {
return err
}
first, _, _ := strings.Cut(filepath.ToSlash(rel), "/")
if ast.IsNodeIDPattern(first) && IsEncryptedBox(first) {
return errors.New("encrypted resources are not supported")View on GitHub (pinned to 9f775e8a12)