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
- Fix the read failure from the wrapped cause — typically chmod u+r .beads/.gitignore or fix its ownership
- Replace a broken symlink at .beads/.gitignore with a real file (bd will recreate the content)
- Re-run after resolving transient network-filesystem/lock issues
- 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
- Don't chown .beads/.gitignore to another user in shared setups
- Avoid symlinking the internal .beads/.gitignore elsewhere
- Preserve u+rw permissions in any provisioning scripts
- Re-check permissions after restoring from backups or rsync with --perms
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
- fs: WriteProjectGitignore: read: %w
- ensure .beads/.gitignore: %w
- read .beads/.gitignore: %w
- failed to read .gitignore: %w
- failed to write .gitignore: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/a4d456ca9344d770.
Report an issue: GitHub.