gastownhall/beads · error

fs: BeadsDirExists: stat %s: %w

Error message

fs: BeadsDirExists: stat %s: %w

What it means

BeadsDirExists reports whether the .beads directory exists. This error is returned when os.Stat on beadsDir fails with anything OTHER than os.ErrNotExist (which is treated as 'does not exist', not an error). It means existence could not be determined at all.

Source

Thrown at internal/storage/domain/fs/beads.go:82

	if r.beadsDir == "" {
		return fmt.Errorf("fs: CreateBeadsDir: beadsDir not resolved")
	}
	if err := os.MkdirAll(r.beadsDir, config.BeadsDirPerm); err != nil {
		return fmt.Errorf("fs: CreateBeadsDir: mkdir %s: %w", r.beadsDir, err)
	}
	if _, err := config.FixBeadsDirPermissions(r.beadsDir); err != nil {
		return fmt.Errorf("fs: CreateBeadsDir: fix perms %s: %w", r.beadsDir, err)
	}
	return nil
}

func (r *beadsDirFSRepositoryImpl) BeadsDirExists(ctx context.Context) (bool, error) {
	info, err := os.Stat(r.beadsDir)
	if errors.Is(err, os.ErrNotExist) {
		return false, nil
	}
	if err != nil {
		return false, fmt.Errorf("fs: BeadsDirExists: stat %s: %w", r.beadsDir, err)
	}
	return info.IsDir(), nil
}

func (r *beadsDirFSRepositoryImpl) WriteBeadsGitignore(ctx context.Context) error {
	if r.templates.BeadsGitignore == "" {
		return fmt.Errorf("fs: WriteBeadsGitignore: template not configured")
	}
	path := filepath.Join(r.beadsDir, ".gitignore")
	// #nosec G304 -- path joined under bound beadsDir
	existing, err := os.ReadFile(path)
	if errors.Is(err, os.ErrNotExist) {
		if werr := os.WriteFile(path, []byte(r.templates.BeadsGitignore), 0600); werr != nil {
			return fmt.Errorf("fs: WriteBeadsGitignore: %w", werr)
		}
		return nil
	}
	if err != nil {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Fix the stat failure indicated by the wrapped cause — typically chmod +x the parent directories so the path becomes traversable
  2. Remove or repair broken/cyclic symlinks along the beadsDir path
  3. Shorten the path if ENAMETOOLONG (move the project out of deeply nested dirs)
  4. Retry if the cause is a transient network-filesystem error

Example fix

// before
$ ls -ld ~/project  # drwx------ root
$ bd ready          // stat ~/project/.beads: permission denied
// after
$ sudo chmod o+x ~/project
$ bd ready  # existence check succeeds
Defensive patterns

Strategy: type-guard

Validate before calling

func beadsDirCheckable(path string) error {
	for dir := path; ; dir = filepath.Dir(dir) {
		if _, err := os.Stat(dir); err != nil {
			if !errors.Is(err, os.ErrNotExist) {
				return fmt.Errorf("cannot traverse %s: %v", dir, err)
			}
		}
		if dir == filepath.Dir(dir) { return nil }
	}
}
if err := beadsDirCheckable(beadsDir); err != nil { return err }
exists, err := repo.BeadsDirExists(ctx)

Type guard

func isStatErr(err error) bool {
	var pe *fs.PathError
	return errors.As(err, &pe) && pe.Op == "stat"
}

Try / catch

exists, err := repo.BeadsDirExists(ctx)
if err != nil {
	if isStatErr(err) && errors.Is(errors.Unwrap(err), syscall.EACCES) {
		return fmt.Errorf("fix parent-directory execute permission first")
	}
	return err
}

Prevention

When it happens

Trigger: Calling BeadsDirExists when os.Stat(r.beadsDir) fails with e.g. EACCES on a parent directory, ELOOP from a symlink cycle, ENAMETOOLONG, or I/O errors — any stat error except ErrNotExist.

Common situations: A parent directory in the path lacks execute permission for the current user; beadsDir contains a symlink loop; overly long path on constrained filesystems; network filesystem temporarily unavailable.

Related errors


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