{"record":{"id":"87d9ff35f138643d","repo":"wavetermdev/waveterm","slug":"failed-to-read-backup-directory-w","errorCode":null,"errorMessage":"failed to read backup directory: %w","messagePattern":"failed to read backup directory: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/filebackup/filebackup.go","lineNumber":135,"sourceCode":"\n\terr = os.WriteFile(restoreToFileName, backupData, perm)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to restore file: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc CleanupOldBackups() error {\n\tbackupBaseDir := filepath.Join(wavebase.GetWaveCachesDir(), \"waveai-backups\")\n\n\tif _, err := os.Stat(backupBaseDir); os.IsNotExist(err) {\n\t\treturn nil\n\t}\n\n\tentries, err := os.ReadDir(backupBaseDir)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to read backup directory: %w\", err)\n\t}\n\n\tcutoffTime := time.Now().Add(-BackupRetentionPeriod)\n\tvar removedCount int\n\n\tfor _, entry := range entries {\n\t\tif !entry.IsDir() {\n\t\t\tcontinue\n\t\t}\n\n\t\tdirPath := filepath.Join(backupBaseDir, entry.Name())\n\t\tinfo, err := entry.Info()\n\t\tif err != nil {\n\t\t\tlog.Printf(\"failed to get info for backup dir %s: %v\\n\", entry.Name(), err)\n\t\t\tcontinue\n\t\t}\n\n\t\tif info.ModTime().Before(cutoffTime) {","sourceCodeStart":117,"sourceCodeEnd":153,"githubUrl":"https://github.com/wavetermdev/waveterm/blob/a4447c1563b2df285ab89e76c82f91e1a1a49c1e/pkg/filebackup/filebackup.go#L117-L153","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","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)."],"exampleFix":"// before\nentries, err := os.ReadDir(backupBaseDir)\nif err != nil {\n\treturn fmt.Errorf(\"failed to read backup directory: %w\", err)\n}\n// after\nentries, err := os.ReadDir(backupBaseDir)\nif err != nil {\n\tif os.IsNotExist(err) {\n\t\treturn nil // directory vanished between Stat and ReadDir; nothing to clean\n\t}\n\treturn fmt.Errorf(\"failed to read backup directory: %w\", err)\n}","handlingStrategy":"try-catch","validationCode":"// Go has no pre-call check API; verify the path is a readable directory first\nif info, err := os.Stat(backupBaseDir); err != nil || !info.IsDir() {\n\t// recreate or fix backupBaseDir before the cleanup loop runs\n\tos.MkdirAll(backupBaseDir, 0o755)\n}","typeGuard":null,"tryCatchPattern":"if err := CleanupOldBackups(ctx); err != nil {\n\tvar pathErr *os.PathError\n\tif errors.As(err, &pathErr) {\n\t\tlog.Printf(\"backup dir unreadable (%s): %v; recreating\", pathErr.Path, pathErr.Err)\n\t\tos.MkdirAll(backupBaseDir, 0o755)\n\t}\n}","preventionTips":["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."],"tags":["filesystem","backup","io"],"backgroundTag":"directory-read-failed","analyzedSha":"a4447c1563b2df285ab89e76c82f91e1a1a49c1e","analyzedAt":"2026-09-01T15:26:23.972Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}