gastownhall/beads · error

%s is not a directory

Error message

%s is not a directory

What it means

After resolving symlinks and stat-ing the root, MeasureDirectorySize verifies the target is a directory; this error is returned when the resolved path exists but is a regular file (or other non-directory). The original (unresolved) root path is included in the message.

Source

Thrown at internal/storage/directory_size.go:32

// MeasureDirectorySize returns the approximate size of the live file tree at
// root. It tolerates descendants disappearing while the tree is being walked,
// but a missing root or any other filesystem error is a failed measurement.
func MeasureDirectorySize(ctx context.Context, root string) (int64, error) {
	if root == "" {
		return 0, fmt.Errorf("directory path is empty")
	}

	resolvedRoot, err := filepath.EvalSymlinks(root)
	if err != nil {
		return 0, err
	}
	info, err := os.Stat(resolvedRoot)
	if err != nil {
		return 0, err
	}
	if !info.IsDir() {
		return 0, fmt.Errorf("%s is not a directory", root)
	}

	return measureDirectorySizeWithWalk(ctx, resolvedRoot, filepath.Walk)
}

func measureDirectorySizeWithWalk(ctx context.Context, root string, walk directoryWalkFunc) (int64, error) {
	var size int64
	err := walk(root, func(path string, info os.FileInfo, walkErr error) error {
		if err := ctx.Err(); err != nil {
			return err
		}
		if walkErr != nil {
			if path != root && errors.Is(walkErr, fs.ErrNotExist) {
				return nil
			}
			return walkErr
		}
		if info == nil {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Point root at the data directory, not a file inside it (stat the path to confirm)
  2. Check symlink targets: fileResolveSymlinks then IsDir; fix or update the symlink
  3. Add a startup check that the configured data directory is a directory before measuring

Example fix

// before
size, err := storage.MeasureDirectorySize(ctx, cfg.DataDir) // DataDir = "/data/beads.db"
// after
if info, serr := os.Stat(cfg.DataDir); serr != nil || !info.IsDir() {
    return fmt.Errorf("%s must be a directory", cfg.DataDir)
}
size, err := storage.MeasureDirectorySize(ctx, cfg.DataDir)
Defensive patterns

Strategy: validation

Validate before calling

func ensureDir(path string) error {
    resolved, err := filepath.EvalSymlinks(path)
    if err != nil { return err }
    info, err := os.Stat(resolved)
    if err != nil { return err }
    if !info.IsDir() { return fmt.Errorf("%s is not a directory", path) }
    return nil
}

Try / catch

if err := ensureDir(cfg.DataDir); err != nil {
    return fmt.Errorf("invalid data dir for measurement: %w", err)
}
size, err := storage.MeasureDirectorySize(ctx, cfg.DataDir)

Prevention

When it happens

Trigger: MeasureDirectorySize(ctx, root) where root points at a file — e.g. passing a database file instead of its containing directory, or a symlink resolving to a file.

Common situations: Config pointing at the .dbell/.dolt database file rather than the data directory; a symlink that used to target a directory now targets a file after a migration; users typo-ing the path to a file.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/afab6ccec338589f. Report an issue: GitHub.