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
- Fix the stat failure indicated by the wrapped cause — typically chmod +x the parent directories so the path becomes traversable
- Remove or repair broken/cyclic symlinks along the beadsDir path
- Shorten the path if ENAMETOOLONG (move the project out of deeply nested dirs)
- 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
- Keep paths short and symlink-free between the repo root and .beads
- Ensure every ancestor directory has the execute bit for the running user
- Watch for path-length limits when nesting deeply on constrained filesystems
- Do not race bd with tools that rename/remove parent directories
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
- checking source sidecar %s: %w
- ensureProxiedServerConfig: stat %s: %w
- failed to stat user config.yaml: %w
- checking dolt metadata directory %s: %w
- checking dolt compatibility marker %s: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/8340e59c4a7c33ed.
Report an issue: GitHub.