{"record":{"id":"66cb2fe291be529b","repo":"gastownhall/beads","slug":"fs-writeprojectgitignore-read-w","errorCode":null,"errorMessage":"fs: WriteProjectGitignore: read: %w","messagePattern":"fs: WriteProjectGitignore: read: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/domain/fs/beads.go","lineNumber":154,"sourceCode":"\treturn missing\n}\n\nfunc (r *beadsDirFSRepositoryImpl) BeadsGitignoreExists(ctx context.Context) (bool, error) {\n\treturn fileExists(filepath.Join(r.beadsDir, \".gitignore\"), \"fs: BeadsGitignoreExists\")\n}\n\nfunc (r *beadsDirFSRepositoryImpl) WriteProjectGitignore(ctx context.Context) error {\n\tif r.workDir == \"\" {\n\t\treturn fmt.Errorf(\"fs: WriteProjectGitignore: workDir not set\")\n\t}\n\tif len(r.templates.ProjectGitignorePatterns) == 0 {\n\t\treturn fmt.Errorf(\"fs: WriteProjectGitignore: patterns not configured\")\n\t}\n\tpath := filepath.Join(r.workDir, \".gitignore\")\n\t// #nosec G304 -- path joined under bound workDir\n\texisting, err := os.ReadFile(path)\n\tif err != nil && !errors.Is(err, os.ErrNotExist) {\n\t\treturn fmt.Errorf(\"fs: WriteProjectGitignore: read: %w\", err)\n\t}\n\n\tvar toAdd []string\n\tfor _, pattern := range r.templates.ProjectGitignorePatterns {\n\t\tif !containsLine(existing, pattern) {\n\t\t\ttoAdd = append(toAdd, pattern)\n\t\t}\n\t}\n\tif len(toAdd) == 0 {\n\t\treturn nil\n\t}\n\n\tvar buf bytes.Buffer\n\tbuf.Write(existing)\n\tif len(existing) > 0 && !bytes.HasSuffix(existing, []byte(\"\\n\")) {\n\t\tbuf.WriteByte('\\n')\n\t}\n\tif header := r.templates.ProjectGitignoreHeader; header != \"\" && !containsLine(existing, header) {","sourceCodeStart":136,"sourceCodeEnd":172,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/domain/fs/beads.go#L136-L172","documentation":"Before merging, WriteProjectGitignore reads the existing project-root .gitignore so it can append only missing patterns. This error wraps an os.ReadFile failure other than ErrNotExist on <workDir>/.gitignore — bd could not read a file that appears to exist.","triggerScenarios":"Calling WriteProjectGitignore when workDir/.gitignore exists but os.ReadFile fails: EACCES, EISDIR, ELOOP from a symlink cycle, or transient I/O error.","commonSituations":"Project .gitignore owned by root or another user with restrictive mode; a symlinked gitignore pointing outside the repo with bad permissions; concurrent tooling locking or removing the file on a network FS.","solutions":["Fix the wrapped cause: chmod u+r <workDir>/.gitignore or correct its ownership","Replace a broken symlink with a normal file; bd will recreate and merge the required patterns","Retry after resolving transient filesystem/lock conditions","As a last resort, move the unreadable file aside, run the write, then re-apply your custom rules"],"exampleFix":"// before\n$ ls -l .gitignore  # -rw------- root\n$ bd init  // fs: WriteProjectGitignore: read: permission denied\n// after\n$ sudo chown $(whoami) .gitignore && chmod u+rw .gitignore\n$ bd init  # missing bd patterns appended","handlingStrategy":"validation","validationCode":"p := filepath.Join(workDir, \".gitignore\")\nif fi, err := os.Stat(p); err == nil && !fi.Mode().Perm()&0o400 != 0 {\n\treturn fmt.Errorf(\"%s unreadable; chmod u+r %s\", p, p)\n}\nrepo.WriteProjectGitignore(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.WriteProjectGitignore(ctx); err != nil {\n\tif strings.Contains(err.Error(), \": read: \") {\n\t\treturn fmt.Errorf(\"%s/.gitignore unreadable; fix ownership/permissions or replace broken symlink\", workDir)\n\t}\n\treturn err\n}","preventionTips":["Keep the project .gitignore owned and readable by the user running bd","Avoid symlink chains for the project .gitignore","Verify permissions after copying repos between machines or restore operations","Close/avoid concurrent tools that rewrite .gitignore with restrictive modes"],"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"}