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

  1. Inspect the wrapped error (errors.Unwrap / errors.Is fs.ErrPermission, fs.ErrNotExist) to identify EACCES vs ENOSPC vs EROFS
  2. Fix ownership and permissions of <workspace>/history so the kernel user can create directories (0755 and writable)
  3. Free disk space or enlarge the volume, then retry — the next generation cycle recreates the dir
  4. 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

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


AI-assisted analysis of siyuan-note/siyuan@afa823b6b4 (2026-08-18). Data as JSON: /api/errors/745ec3feafecc433. Report an issue: GitHub.