gastownhall/beads · error

fs: WriteBeadsGitignore: read: %w

Error message

fs: WriteBeadsGitignore: read: %w

What it means

WriteBeadsGitignore reads the existing .beads/.gitignore to merge missing template patterns (append-only policy). This error wraps an os.ReadFile failure other than ErrNotExist, meaning bd could not read a file that stat suggested exists.

Source

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

	}
	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 {
		return fmt.Errorf("fs: WriteBeadsGitignore: read: %w", err)
	}
	// Existing file: append-only. A wholesale rewrite to the template
	// destroys local rules (e.g. export negations) the user added — the
	// gitignore is shared state, not bd-owned (bd-kaaz3).
	missing := missingTemplatePatternLines(string(existing), r.templates.BeadsGitignore)
	if len(missing) == 0 {
		return nil
	}
	content := string(existing)
	if len(content) > 0 && !strings.HasSuffix(content, "\n") {
		content += "\n"
	}
	content += "\n# Added by bd (missing required patterns)\n" + strings.Join(missing, "\n") + "\n"
	if err := os.WriteFile(path, []byte(content), 0600); err != nil {
		return fmt.Errorf("fs: WriteBeadsGitignore: %w", err)
	}
	return nil
}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Fix the read failure from the wrapped cause — typically chmod u+r .beads/.gitignore or fix its ownership
  2. Replace a broken symlink at .beads/.gitignore with a real file (bd will recreate the content)
  3. Re-run after resolving transient network-filesystem/lock issues
  4. As a last resort delete the unreadable file and let bd write a fresh one, re-adding any custom patterns afterwards

Example fix

// before
$ ls -l .beads/.gitignore  # -rw------- root
$ bd init  // fs: WriteBeadsGitignore: read: permission denied
// after
$ sudo chown $(whoami) .beads/.gitignore && chmod u+rw .beads/.gitignore
$ bd init  # patterns merged append-only
Defensive patterns

Strategy: validation

Validate before calling

p := filepath.Join(beadsDir, ".gitignore")
if fi, err := os.Stat(p); err == nil && !fi.Mode().Perm()&0o400 != 0 {
	return fmt.Errorf("%s not readable by current user; chmod u+r it", p)
}
repo.WriteBeadsGitignore(ctx)

Type guard

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

Try / catch

if err := repo.WriteBeadsGitignore(ctx); err != nil {
	if strings.Contains(err.Error(), ": read: ") {
		return fmt.Errorf(".beads/.gitignore unreadable; chown/chmod it or replace a broken symlink")
	}
	return err
}

Prevention

When it happens

Trigger: Calling WriteBeadsGitignore when .beads/.gitignore exists but os.ReadFile fails: EACCES (file not readable), EISDIR variants, ELOOP, or I/O error during the read.

Common situations: .gitignore inside .beads owned by another user with restrictive mode; a symlinked gitignore pointing nowhere permitted; file locked/removed concurrently by another process on a network FS.

Related errors


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