wavetermdev/waveterm · error

failed to read directory: %w

Error message

failed to read directory: %w

What it means

ReadDir wraps errors from os.ReadDir. Unlike stat, this can fail even when the path is a valid directory if the directory cannot be opened — typically a permissions problem (no read bit) or the directory was removed between stat and read. The OS cause is preserved via %w.

Source

Thrown at pkg/util/fileutil/readdir.go:55

func ReadDir(path string, maxEntries int) (*ReadDirResult, error) {
	expandedPath, err := wavebase.ExpandHomeDir(path)
	if err != nil {
		return nil, fmt.Errorf("failed to expand path: %w", err)
	}

	fileInfo, err := os.Stat(expandedPath)
	if err != nil {
		return nil, fmt.Errorf("failed to stat path: %w", err)
	}

	if !fileInfo.IsDir() {
		return nil, fmt.Errorf("path is not a directory")
	}

	entries, err := os.ReadDir(expandedPath)
	if err != nil {
		return nil, fmt.Errorf("failed to read directory: %w", err)
	}

	totalEntries := len(entries)

	isDirMap := make(map[string]bool)
	symlinkCount := 0
	for _, entry := range entries {
		name := entry.Name()
		if entry.Type()&fs.ModeSymlink != 0 {
			if symlinkCount < 1000 {
				symlinkCount++
				fullPath := filepath.Join(expandedPath, name)
				if info, err := os.Stat(fullPath); err == nil {
					isDirMap[name] = info.IsDir()
				} else {
					isDirMap[name] = entry.IsDir()
				}
			} else {

View on GitHub (pinned to a4447c1563)

Solutions

  1. Unwrap the cause: 'permission denied' -> chmod/chown the directory or run as a user with read access.
  2. Re-check the directory still exists; if it races away (e.g. under /proc or a tmp build dir), add existence handling and retry.
  3. Use `sudo -u <user> ls <dir>` to reproduce the access the library sees.
  4. If listing is optional, catch this error and degrade gracefully instead of failing the whole operation.

Example fix

// before: no error differentiation
res, err := fileutil.ReadDir(dir, 100)
// after
res, err := fileutil.ReadDir(dir, 100)
if err != nil {
	if os.IsPermission(errors.Unwrap(err)) {
		log.Warnf("skipping unreadable dir %s", dir)
		return nil
	}
	return err
}
Defensive patterns

Strategy: try-catch

Validate before calling

if f, err := os.Open(path); err != nil {
	return fmt.Errorf("directory not readable: %w", err)
} else { f.Close() }

Try / catch

res, err := fileutil.ReadDir(path, max)
if err != nil {
	var perr *fs.PathError
	if errors.As(err, &perr) && errors.Is(perr.Err, os.ErrPermission) {
		return nil // skip unreadable dir gracefully
	}
	return err
}

Prevention

When it happens

Trigger: os.ReadDir fails on an existing, statable directory: missing read (r) permission, directory deleted in a race, or I/O error on the underlying storage.

Common situations: Listing /root or another user's private directory as a normal user; container/agent sandbox without read access; directories like /proc/<pid> that vanish; NFS stale handles.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01). Data as JSON: /api/errors/a04598c7fe2b8d78. Report an issue: GitHub.