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
- Read the wrapped cause; if it is chmod permission denied, chown/chmod the .beads directory yourself: sudo chown -R $(whoami) .beads
- 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
- On filesystems without POSIX perms (some network mounts), place the project on a local filesystem or relax the permission expectation
- 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
- Create .beads yourself with correct ownership before automated runs if the environment pre-creates it
- Avoid pre-created directories from other users in CI caches
- Use local POSIX filesystems for bead storage; avoid NFS roots with root-squash
- Check for symlinks pointing to foreign-owned targets
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
- failed to chmod %s to %04o: %w
- dolt path is not executable
- failed to create backup directory: %w
- failed to create temp file: %w
- create beads directory: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/b26a43fd7f49fdee.
Report an issue: GitHub.