wavetermdev/waveterm · error
failed to read backup directory
Error message
failed to read backup directory: %w
What it means
CleanupOldBackups walks backupBaseDir to delete backups older than BackupRetentionPeriod. Before iterating it calls os.ReadDir(backupBaseDir); if the OS-level read fails (permissions, the path being a file, or a race where the directory disappears between the os.Stat existence check and the ReadDir), the underlying error is wrapped with this message and returned to the cleanup loop.
Solutions
- Check the wrapped cause (%w) to identify the OS error; for permission problems fix ownership/permissions on the backup directory (e.g. chown/chmod so the running user can read it).
- Verify backupBaseDir is actually a directory: ls -la <backupBaseDir>; recreate it with mkdir if it is a file or missing.
- If a race with directory removal is expected, treat fs.ErrNotExist from ReadDir as benign (nothing to clean) in the caller.
- Check mount/disk health if the cause is an I/O error (dmesg, remount read-write).
Example fix
// before
entries, err := os.ReadDir(backupBaseDir)
if err != nil {
return fmt.Errorf("failed to read backup directory: %w", err)
}
// after
entries, err := os.ReadDir(backupBaseDir)
if err != nil {
if os.IsNotExist(err) {
return nil // directory vanished between Stat and ReadDir; nothing to clean
}
return fmt.Errorf("failed to read backup directory: %w", err)
} Defensive patterns
Strategy: try-catch
Validate before calling
// Go has no pre-call check API; verify the path is a readable directory first
if info, err := os.Stat(backupBaseDir); err != nil || !info.IsDir() {
// recreate or fix backupBaseDir before the cleanup loop runs
os.MkdirAll(backupBaseDir, 0o755)
} Try / catch
if err := CleanupOldBackups(ctx); err != nil {
var pathErr *os.PathError
if errors.As(err, &pathErr) {
log.Printf("backup dir unreadable (%s): %v; recreating", pathErr.Path, pathErr.Err)
os.MkdirAll(backupBaseDir, 0o755)
}
} Prevention
- Ensure the process user owns or can read the backup directory at startup.
- Provision backupBaseDir as a directory during install; never point it at a file.
- Treat fs.ErrNotExist from cleanup as benign in periodic loops so races don't alarm.
- Monitor mount health/read-only-remounts of the volume holding backups.
When it happens
Trigger: os.ReadDir(backupBaseDir) fails after the preceding os.Stat found the path exists: permissions changed, backupBaseDir was replaced by a regular file or symlink to a file, the directory was removed by another process between Stat and ReadDir, or an I/O error on the filesystem.
Common situations: Backups directory owned by another user or read-only after a restore; a misconfiguration points backupBaseDir at a file instead of a directory; cleanup loop racing with a wipe/reinstall of the backup directory; read-only mounts (e.g. container volume, disk failure).
Related errors
- error writing to file
- failed to create backup directory
- failed to read directory
- failed to read file for backup
- failed to read file
AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01).
Data as JSON: /api/errors/87d9ff35f138643d.
Report an issue: GitHub.
Appendix: source
Thrown at pkg/filebackup/filebackup.go:135
err = os.WriteFile(restoreToFileName, backupData, perm)
if err != nil {
return fmt.Errorf("failed to restore file: %w", err)
}
return nil
}
func CleanupOldBackups() error {
backupBaseDir := filepath.Join(wavebase.GetWaveCachesDir(), "waveai-backups")
if _, err := os.Stat(backupBaseDir); os.IsNotExist(err) {
return nil
}
entries, err := os.ReadDir(backupBaseDir)
if err != nil {
return fmt.Errorf("failed to read backup directory: %w", err)
}
cutoffTime := time.Now().Add(-BackupRetentionPeriod)
var removedCount int
for _, entry := range entries {
if !entry.IsDir() {
continue
}
dirPath := filepath.Join(backupBaseDir, entry.Name())
info, err := entry.Info()
if err != nil {
log.Printf("failed to get info for backup dir %s: %v\n", entry.Name(), err)
continue
}
if info.ModTime().Before(cutoffTime) {View on GitHub (pinned to a4447c1563)