{"record":{"id":"a4d456ca9344d770","repo":"gastownhall/beads","slug":"fs-writebeadsgitignore-read-w","errorCode":null,"errorMessage":"fs: WriteBeadsGitignore: read: %w","messagePattern":"fs: WriteBeadsGitignore: read: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/domain/fs/beads.go","lineNumber":101,"sourceCode":"\t}\n\treturn info.IsDir(), nil\n}\n\nfunc (r *beadsDirFSRepositoryImpl) WriteBeadsGitignore(ctx context.Context) error {\n\tif r.templates.BeadsGitignore == \"\" {\n\t\treturn fmt.Errorf(\"fs: WriteBeadsGitignore: template not configured\")\n\t}\n\tpath := filepath.Join(r.beadsDir, \".gitignore\")\n\t// #nosec G304 -- path joined under bound beadsDir\n\texisting, err := os.ReadFile(path)\n\tif errors.Is(err, os.ErrNotExist) {\n\t\tif werr := os.WriteFile(path, []byte(r.templates.BeadsGitignore), 0600); werr != nil {\n\t\t\treturn fmt.Errorf(\"fs: WriteBeadsGitignore: %w\", werr)\n\t\t}\n\t\treturn nil\n\t}\n\tif err != nil {\n\t\treturn fmt.Errorf(\"fs: WriteBeadsGitignore: read: %w\", err)\n\t}\n\t// Existing file: append-only. A wholesale rewrite to the template\n\t// destroys local rules (e.g. export negations) the user added — the\n\t// gitignore is shared state, not bd-owned (bd-kaaz3).\n\tmissing := missingTemplatePatternLines(string(existing), r.templates.BeadsGitignore)\n\tif len(missing) == 0 {\n\t\treturn nil\n\t}\n\tcontent := string(existing)\n\tif len(content) > 0 && !strings.HasSuffix(content, \"\\n\") {\n\t\tcontent += \"\\n\"\n\t}\n\tcontent += \"\\n# Added by bd (missing required patterns)\\n\" + strings.Join(missing, \"\\n\") + \"\\n\"\n\tif err := os.WriteFile(path, []byte(content), 0600); err != nil {\n\t\treturn fmt.Errorf(\"fs: WriteBeadsGitignore: %w\", err)\n\t}\n\treturn nil\n}","sourceCodeStart":83,"sourceCodeEnd":119,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/domain/fs/beads.go#L83-L119","documentation":"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.","triggerScenarios":"Calling WriteBeadsGitignore when .beads/.gitignore exists but os.ReadFile fails: EACCES (file not readable), EISDIR variants, ELOOP, or I/O error during the read.","commonSituations":".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.","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"],"exampleFix":"// before\n$ ls -l .beads/.gitignore  # -rw------- root\n$ bd init  // fs: WriteBeadsGitignore: read: permission denied\n// after\n$ sudo chown $(whoami) .beads/.gitignore && chmod u+rw .beads/.gitignore\n$ bd init  # patterns merged append-only","handlingStrategy":"validation","validationCode":"p := filepath.Join(beadsDir, \".gitignore\")\nif fi, err := os.Stat(p); err == nil && !fi.Mode().Perm()&0o400 != 0 {\n\treturn fmt.Errorf(\"%s not readable by current user; chmod u+r it\", p)\n}\nrepo.WriteBeadsGitignore(ctx)","typeGuard":"func isReadErr(err error) bool {\n\tvar pe *fs.PathError\n\treturn errors.As(err, &pe) && pe.Op == \"open\"\n}","tryCatchPattern":"if err := repo.WriteBeadsGitignore(ctx); err != nil {\n\tif strings.Contains(err.Error(), \": read: \") {\n\t\treturn fmt.Errorf(\".beads/.gitignore unreadable; chown/chmod it or replace a broken symlink\")\n\t}\n\treturn err\n}","preventionTips":["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"],"tags":["filesystem","read","gitignore","permissions"],"backgroundTag":"file-read-permission-denied","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}