wavetermdev/waveterm · error

path is not a directory

Error message

path is not a directory

What it means

ReadDir lists directory contents, so after stat succeeds it asserts fileInfo.IsDir(). If the expanded path is a regular file, symlink-to-file, device, etc., this plain error is returned with no path embedded. The message is intentionally terse; the caller is expected to have passed a directory.

Source

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

	Entries      []DirEntryOut `json:"entries"`
	EntryCount   int           `json:"entry_count"`
	TotalEntries int           `json:"total_entries"`
	Truncated    bool          `json:"truncated,omitempty"`
}

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 {

View on GitHub (pinned to a4447c1563)

Solutions

  1. Pass the containing directory instead: strip the filename from the path.
  2. Stat the path in the caller and branch: use ReadDir for directories and a file-read API for files.
  3. If the target is a symlink to a directory it works fine; only symlink-to-file triggers this — resolve the real target to confirm.
  4. Add UI/logic validation that the user-selected path is a directory before invoking ReadDir.

Example fix

// before
fileutil.ReadDir("/etc/passwd", 100)
// after
fileutil.ReadDir("/etc", 100)
Defensive patterns

Strategy: validation

Validate before calling

info, err := os.Stat(path)
if err != nil { return err }
if !info.IsDir() {
	return fmt.Errorf("%s is a file; pass its parent directory", path)
}

Type guard

func isDir(path string) bool {
	info, err := os.Stat(path)
	return err == nil && info.IsDir()
}

Try / catch

res, err := fileutil.ReadDir(path, max)
if err != nil && err.Error() == "path is not a directory" {
	// switch to a file-read API or use filepath.Dir(path)
}

Prevention

When it happens

Trigger: Calling ReadDir with a file path instead of a directory path — e.g. ReadDir("/etc/passwd", 100) or ReadDir("main.go", ...).

Common situations: A variable holds a file path where a directory was expected; shell-style completion handed back a file; a symlink resolves to a file; code that assumed a config value is always a folder.

Related errors


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