gastownhall/beads · error

fs: CreateBeadsDir: fix perms %s: %w

Error message

fs: CreateBeadsDir: fix perms %s: %w

What it means

This error wraps a failure from config.FixBeadsDirPermissions, called right after the .beads directory is created to normalize its permissions. The directory now exists, but bd could not chmod/fix it to the expected mode. The wrapped error carries the OS-level cause.

Source

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

	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
}

func (r *beadsDirFSRepositoryImpl) WriteBeadsGitignore(ctx context.Context) error {
	if r.templates.BeadsGitignore == "" {
		return fmt.Errorf("fs: WriteBeadsGitignore: template not configured")

View on GitHub (pinned to 71377f2769)

Solutions

  1. Read the wrapped cause; if it is chmod permission denied, chown/chmod the .beads directory yourself: sudo chown -R $(whoami) .beads
  2. If the directory was created by another user or install step, remove it and re-run init so bd creates it fresh with correct ownership
  3. On filesystems without POSIX perms (some network mounts), place the project on a local filesystem or relax the permission expectation
  4. Verify beadsDir is not a symlink to a location owned by a different user

Example fix

// before (dir owned by root)
$ sudo mkdir .beads && bd init
// fs: CreateBeadsDir: fix perms .beads: chmod ...: permission denied
// after
$ sudo chown -R $(whoami) .beads
$ bd init  # succeeds
Defensive patterns

Strategy: validation

Validate before calling

if info, err := os.Stat(beadsDir); err == nil && info.IsDir() {
	if st, ok := info.Sys().(*syscall.Stat_t); ok {
		if int(st.Uid) != os.Getuid() {
			return fmt.Errorf("%s owned by uid %d; chown before running bd", beadsDir, st.Uid)
		}
	}
}
repo.CreateBeadsDir(ctx)

Type guard

func isFixPermsErr(err error) bool {
	return strings.Contains(err.Error(), "fix perms")
}

Try / catch

if err := repo.CreateBeadsDir(ctx); err != nil {
	if strings.Contains(err.Error(), "fix perms") {
		log.Warn(".beads exists but not owned by current user; run: chown -R $(whoami) .beads")
	}
	return err
}

Prevention

When it happens

Trigger: Calling CreateBeadsDir when config.FixBeadsDirPermissions(r.beadsDir) returns an error: chmod denied because the process does not own the (pre-existing) directory, or an underlying stat/open of the directory fails.

Common situations: .beads was pre-created by root or another user so a non-root bd cannot chmod it; NFS/Windows filesystems that do not support POSIX permission changes; mounted volumes with enforced ownership.

Related errors


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