gastownhall/beads · error

fs: CreateBeadsDir: beadsDir not resolved

Error message

fs: CreateBeadsDir: beadsDir not resolved

What it means

CreateBeadsDir creates the .beads directory (MkdirAll + permission fix), but the repository implementation guards that its beadsDir field was actually resolved during construction. This error is an internal invariant failure: a beadsDirFSRepositoryImpl was built without a resolved beads directory path, so creating it is unsafe.

Source

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

	return domain.BeadsDirResolution{BeadsDir: r.beadsDir, HasExplicit: r.hasExplicit}
}

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)
	}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Construct the repository via the normal factory/resolution path so beadsDir is set (or pass an explicit beads directory)
  2. Ensure bd is run inside a project root where .beads can be resolved, or pass the beads dir explicitly
  3. If you own the construction site, fail fast at construction when resolution returns an empty path instead of deferring to CreateBeadsDir

Example fix

// before
repo := &beadsDirFSRepositoryImpl{} // beadsDir unresolved
repo.CreateBeadsDir(ctx)
// after
repo := newBeadsDirFSRepository(resolvedBeadsDir) // factory resolves path
if resolvedBeadsDir == "" { return errors.New("beads dir could not be resolved") }
repo.CreateBeadsDir(ctx)
Defensive patterns

Strategy: validation

Validate before calling

if beadsDir == "" {
    return errors.New("beads dir not resolved; pass an explicit path or run inside a project root")
}

Type guard

func beadsDirReady(r *fs.BeadsDirFSRepository, dir string) bool { return dir != "" }

Try / catch

if err := repo.CreateBeadsDir(ctx); err != nil {
    if strings.Contains(err.Error(), "beadsDir not resolved") {
        // construction bug: rebuild via the factory with a resolved path
    }
    return err
}

Prevention

When it happens

Trigger: Calling CreateBeadsDir on a beadsDirFSRepositoryImpl constructed with an empty beadsDir — typically a construction/configuration bug where directory discovery (repo-root walk / explicit flag) failed silently or the constructor skipped resolution.

Common situations: Embedding beads as a library and instantiating the FS repository directly without running the directory-resolution step; running bd outside any git project root such that discovery yields nothing; misconfigured --path handling.

Related errors


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