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
- Check the path is a regular file and readable: `ls -la .beads/.gitignore` and `cat .beads/.gitignore`.
- If it is a directory, remove it (`rm -rf .beads/.gitignore`) and re-run the fix so the template is written.
- Fix permissions: `chmod u+rw .beads/.gitignore` or re-own the files (`chown -R $(whoami) .beads`).
- 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
- Run all bd commands as the user who owns the checkout (avoid sudo/root container runs).
- Never create a directory named .gitignore; sync tools can do this by mistake.
- Keep .beads file permissions at 0644/0755 in shared checkouts.
- Re-check .beads contents after WSL/Windows cross-filesystem operations.
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
- ensure .beads/.gitignore: %w
- unable to read plugin file: %w
- creating .bd-dolt-ok marker: %w
- chmod .beads/.gitignore: %w
- ensure .beads/.gitignore: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/b7ac84627ad39e42.
Report an issue: GitHub.