gastownhall/beads · error

fs: CreateBeadsDir: mkdir %s: %w

Error message

fs: CreateBeadsDir: mkdir %s: %w

What it means

This error wraps a failure from os.MkdirAll when creating the .beads directory. It is thrown by CreateBeadsDir when the directory cannot be created at the resolved beadsDir path (permissions, parent missing in a read-only tree, path is a file, etc.). The wrapped OS error (%w) carries the underlying cause.

Source

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

func (r *beadsDirFSRepositoryImpl) BeadsDirIsLocal(ctx context.Context) bool {
	workDir := filepath.Clean(utils.CanonicalizePath(r.workDir))
	beadsDir := filepath.Clean(utils.CanonicalizePath(r.beadsDir))
	if beadsDir == workDir {
		return true
	}
	rel, err := filepath.Rel(workDir, beadsDir)
	if err != nil {
		return false
	}
	return rel != ".." && !strings.HasPrefix(rel, ".."+string(filepath.Separator))
}

func (r *beadsDirFSRepositoryImpl) CreateBeadsDir(ctx context.Context) error {
	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
}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check the wrapped %w cause in the error message (e.g. 'permission denied', 'not a directory') and fix the filesystem condition directly
  2. Verify the parent path of beadsDir exists and is writable: ls -ld $(dirname <beadsDir>) and mkdir -p it manually if needed
  3. If a regular file exists at beadsDir, remove or rename it so the directory can be created
  4. Ensure the process user has write permission on the target location, or run from a writable working directory

Example fix

// before
repo := fsrepo.New(fsrepo.WithBeadsDir("/proc/self/beads"))
repo.CreateBeadsDir(ctx) // mkdir /proc/self/beads: permission denied
// after
beadsDir := filepath.Join(os.Getenv("HOME"), "project", ".beads")
os.MkdirAll(filepath.Dir(beadsDir), 0o755) // ensure writable parent
repo.CreateBeadsDir(ctx)
Defensive patterns

Strategy: validation

Validate before calling

func canCreateDir(path string) error {
	fi, err := os.Stat(path)
	if err == nil && !fi.IsDir() {
		return fmt.Errorf("%s is a file, not a directory", path)
	}
	for dir := filepath.Dir(path); ; dir = filepath.Dir(dir) {
		if err := unix.Access(dir, unix.W_OK|unix.X_OK); err != nil {
			return fmt.Errorf("%s not writable: %v", dir, err)
		}
		if dir == filepath.Dir(dir) { return nil }
	}
}
if err := canCreateDir(beadsDir); err != nil { return err }
repo.CreateBeadsDir(ctx)

Type guard

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

Try / catch

if err := repo.CreateBeadsDir(ctx); err != nil {
	var pe *fs.PathError
	if errors.As(err, &pe) && errors.Is(pe.Err, syscall.EACCES) {
		return fmt.Errorf("cannot create %s: check ownership/permissions of parent dirs", pe.Path)
	}
	return err
}

Prevention

When it happens

Trigger: Calling CreateBeadsDir (e.g. via bd init) when os.MkdirAll(r.beadsDir, config.BeadsDirPerm) fails: parent directory does not exist and cannot be created, permission denied, path component is a regular file, disk full, or read-only filesystem.

Common situations: Running bd inside a read-only mount or container filesystem; beadsDir points under a path owned by another user; a stale file named .beads exists where the directory should be; umask/capability restrictions in CI sandboxes.

Related errors


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