gastownhall/beads · error

read .beads/.gitignore: %w

Error message

read .beads/.gitignore: %w

What it means

EnsureGitignoreForBeadsDir reads .beads/.gitignore and appends any runtime patterns bd requires. A read error that is NOT os.IsNotExist (the not-exist case is handled by writing a template) is wrapped as "read .beads/.gitignore". So this error means the file exists but could not be read — a permission or I/O problem, never a missing file.

Source

Thrown at cmd/bd/doctor/gitignore.go:189

		Name:    "Gitignore",
		Status:  "ok",
		Message: "Up to date",
	}
}

// EnsureGitignoreForBeadsDir writes the canonical .beads/.gitignore when it is
// missing or outdated. If the file does not exist, it writes the full template.
// If it exists but is outdated, it safely appends missing required patterns so
// local additions are preserved.
func EnsureGitignoreForBeadsDir(beadsDir string) error {
	gitignorePath := filepath.Join(beadsDir, ".gitignore")

	content, err := os.ReadFile(gitignorePath) // #nosec G304 -- caller supplies the active .beads dir
	if os.IsNotExist(err) {
		return writeGitignoreTemplate(gitignorePath)
	}
	if err != nil {
		return fmt.Errorf("read .beads/.gitignore: %w", err)
	}

	missing := missingGitignorePatterns(string(content))
	if len(missing) == 0 {
		return nil
	}

	if info, err := os.Stat(gitignorePath); err == nil {
		if info.Mode().Perm()&0200 == 0 {
			if err := os.Chmod(gitignorePath, 0600); err != nil {
				return fmt.Errorf("chmod .beads/.gitignore: %w", err)
			}
		}
	}

	existingContent := string(content)
	newContent := existingContent
	if len(newContent) > 0 && !strings.HasSuffix(newContent, "\n") {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check the path is a regular file and readable: `ls -la .beads/.gitignore` and `cat .beads/.gitignore`.
  2. If it is a directory, remove it (`rm -rf .beads/.gitignore`) and re-run the fix so the template is written.
  3. Fix permissions: `chmod u+rw .beads/.gitignore` or re-own the files (`chown -R $(whoami) .beads`).
  4. If root-owned from a container run, redo the operation as the same user or fix ownership.

Example fix

// before
$ ls -la .beads/.gitignore
drwxr-xr-x  .gitignore   # accidentally a directory

// after
$ rm -rf .beads/.gitignore
$ bd doctor fix-gitignore
wrote .beads/.gitignore with required patterns
Defensive patterns

Strategy: validation

Validate before calling

info, err := os.Stat(".beads/.gitignore")
if err == nil && info.IsDir() {
	os.RemoveAll(".beads/.gitignore") // replace accidental directory
}
if err == nil && info.Mode().Perm()&0400 == 0 {
	os.Chmod(".beads/.gitignore", 0o644)
}

Try / catch

if err := EnsureGitignoreForBeadsDir(beadsDir); err != nil {
	if strings.HasPrefix(err.Error(), "read .beads/.gitignore") {
		// permission/I-O problem — check ownership and that it is a regular file
	}
	return err
}

Prevention

When it happens

Trigger: os.ReadFile(.beads/.gitignore) fails with a non-ENOENT error: the path is a directory named .gitignore, the file has restrictive permissions (unreadable by the current user), an I/O error occurs on the disk, or the file descriptor limit is exhausted.

Common situations: A directory was accidentally created at .beads/.gitignore (e.g. by a misconfigured sync tool); the repo was checked out by another user (root in a container) leaving root-owned files; filesystem mount issues or read-only remounts; Windows/WSL permission mismatches.

Related errors


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