siyuan-note/siyuan · error
get history directory failed: %w
Error message
get history directory failed: %w
What it means
createAssetsHistory (kernel/model/history.go:888-891) wraps the failure of getHistoryDir(HistoryOpUpdate), which is just os.MkdirAll(history/<yyyyMMdd-HHmmss>-update) (history.go:1161-1168). The %w chain preserves the underlying filesystem error, so the real cause — usually EACCES, EROFS, or ENOSPC — rides along in errors.Unwrap.
Source
Thrown at kernel/model/history.go:890
assetAbsPath := filepath.Join(util.DataDir, filepath.FromSlash(assetPath))
assetsDir := filepath.Join(util.DataDir, "assets")
if !gulu.File.IsSubPath(assetsDir, assetAbsPath) {
return errors.New("asset path must be under assets")
}
info, statErr := os.Stat(assetAbsPath)
if statErr != nil {
return statErr
}
if info.IsDir() {
return errors.New("asset path must be a file")
}
return createAssetsHistory([]string{assetAbsPath})
}
func createAssetsHistory(assets []string) (err error) {
historyDir, err := getHistoryDir(HistoryOpUpdate)
if err != nil {
return fmt.Errorf("get history directory failed: %w", err)
}
for _, file := range assets {
assetRelPath, relErr := filepath.Rel(filepath.Join(util.DataDir, "assets"), file)
if relErr != nil || assetRelPath == "." || strings.HasPrefix(assetRelPath, ".."+string(filepath.Separator)) {
return errors.New("asset path must be under assets")
}
historyPath := filepath.Join(historyDir, "assets", assetRelPath)
if err = os.MkdirAll(filepath.Dir(historyPath), 0755); err != nil {
return fmt.Errorf("create history directory [%s] failed: %w", filepath.Dir(historyPath), err)
}
if err = filelock.Copy(file, historyPath); err != nil {
if os.IsNotExist(err) {
continue
}
return fmt.Errorf("copy asset [%s] to [%s] failed: %w", file, historyPath, err)
}View on GitHub (pinned to afa823b6b4)
Solutions
- Inspect the wrapped error (errors.Unwrap / errors.Is fs.ErrPermission, fs.ErrNotExist) to identify EACCES vs ENOSPC vs EROFS
- Fix ownership and permissions of <workspace>/history so the kernel user can create directories (0755 and writable)
- Free disk space or enlarge the volume, then retry — the next generation cycle recreates the dir
- If the volume is read-only by mistake, remount it rw and restart the kernel
Defensive patterns
Strategy: try-catch
Validate before calling
if err := ensureDirWritable(filepath.Join(util.WorkspaceDir, "history")); err != nil {
return fmt.Errorf("history dir not writable, fix before snapshot: %w", err)
}
return model.CreateAssetHistory(assetPath)
func ensureDirWritable(dir string) error {
if err := os.MkdirAll(dir, 0755); err != nil {
return err
}
probe := filepath.Join(dir, ".probe")
if err := os.WriteFile(probe, nil, 0644); err != nil {
return err
}
return os.Remove(probe)
} Try / catch
err := model.CreateAssetHistory(p)
if err != nil && strings.Contains(err.Error(), "get history directory failed") {
if errors.Is(err, fs.ErrPermission) { /* fix ownership of history/ */ }
if errors.Is(err, fs.ErrNoSpace) { /* free disk and retry */ }
} Prevention
- Monitor free space on the workspace volume
- Verify the kernel process owns/can write history/ after migrations and container restarts
- Probe writability at startup in ops scripts
When it happens
Trigger: The workspace history/ directory is not writable by the kernel process (wrong owner after a copy, root-owned from a Docker volume), the volume is mounted read-only, or the disk is full when the daily asset history generation or an explicit createAssetHistory call runs.
Common situations: Docker installs with incorrect volume ownership; workspaces migrated between users preserving bad permissions; full disks during large media snapshots; SELinux/AppArmor denials on the workspace.
Related errors
- create history directory [%s] failed: %w
- failed to make directory: %w
- invalid frontend capability ID: %s
- unsupported agent runtime schema version: %d
- write data [%s] failed: %s
AI-assisted analysis of siyuan-note/siyuan@afa823b6b4 (2026-08-18).
Data as JSON: /api/errors/745ec3feafecc433.
Report an issue: GitHub.