{"record":{"id":"bdde1f5430cc243e","repo":"gastownhall/beads","slug":"failed-to-write-gitignore-w-bdde1f","errorCode":null,"errorMessage":"failed to write .gitignore: %w","messagePattern":"failed to write \\.gitignore: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/bd/init_stealth.go","lineNumber":221,"sourceCode":"\t\t}\n\t\tout = append(out, lines[i])\n\t}\n\tif !changed {\n\t\treturn false, nil\n\t}\n\n\tnewContent := strings.Join(out, \"\\n\")\n\tif strings.TrimSpace(newContent) == \"\" {\n\t\t// beads was the only reason this .gitignore existed — remove it for true stealth.\n\t\tif err := os.Remove(gitignorePath); err != nil {\n\t\t\treturn false, fmt.Errorf(\"failed to remove emptied .gitignore: %w\", err)\n\t\t}\n\t\treturn true, nil\n\t}\n\n\t// #nosec G306 - gitignore needs to be readable by git and collaborators\n\tif err := os.WriteFile(gitignorePath, []byte(newContent), 0644); err != nil {\n\t\treturn false, fmt.Errorf(\"failed to write .gitignore: %w\", err)\n\t}\n\treturn true, nil\n}\n\n// isStealthRepo reports whether beads must keep its footprint out of tracked git files for the\n// workspace at repoPath. It keys off the persisted no-git-ops flag — the same signal bd prime uses\n// for the stealth session-close protocol (GH#593). bd init --stealth sets it, and a user may also\n// set it directly; either way beads routes ignores into .git/info/exclude rather than a tracked\n// .gitignore.\nfunc isStealthRepo(repoPath string) bool {\n\tbeadsDir := doctor.ResolveBeadsDirForRepo(repoPath)\n\treturn config.GetStringFromDir(beadsDir, \"no-git-ops\") == \"true\"\n}\n\n// trackedGitignoreHasBeadsSection reports whether the tracked project-root .gitignore at repoPath\n// still carries the bd-managed section header — i.e. a previous run leaked Dolt patterns into a\n// git-visible file. Used by the stealth doctor check to flag the leak for --fix to clean up.\nfunc trackedGitignoreHasBeadsSection(repoPath string) bool {","sourceCodeStart":203,"sourceCodeEnd":239,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/cmd/bd/init_stealth.go#L203-L239","documentation":"When user content remains after stripping the beads section, the function rewrites .gitignore via os.WriteFile with mode 0644. Any write error (permission denied, read-only filesystem, disk full, path turned into a directory) is wrapped with %w and returned. This is the commit-step of the cleanup: the in-memory newContent was built but could not be persisted.","triggerScenarios":"os.WriteFile(gitignorePath, ...) returns an error: no write permission on .gitignore, no write permission on the containing directory (needed for truncate/rename semantics), read-only mount, ENOSPC (disk full), or .gitignore is actually a directory.","commonSituations":".gitignore owned by root or another user while bd runs unprivileged; CI workspaces mounted read-only; disk quota/full disk on the dev machine; editor or sync tool (Dropbox/OneDrive) temporarily locking the file; SELinux/AppArmor denying writes to the repo path.","solutions":["Fix ownership/permissions: sudo chown $(whoami) .gitignore && chmod 644 .gitignore, and ensure the repo directory is writable (chmod u+w).","Free disk space / check quota if the error wraps ENOSPC (df -h).","If the mount is read-only, remount or clone the repo into a writable path.","Disable or pause file-sync/AV tools that lock .gitignore, then retry the fix.","Manually edit .gitignore to remove the beads section, then re-run bd stealth fix to verify."],"exampleFix":"// before (file owned by root)\n-rw-r--r-- 1 root root 512 .gitignore\n\n// after\n$ sudo chown $(whoami) .gitignore && chmod 644 .gitignore\n$ bd stealth fix  # os.WriteFile succeeds","handlingStrategy":"try-catch","validationCode":"if info, err := os.Stat(gitignorePath); err == nil {\n\tif info.Mode().Perm()&0200 == 0 {\n\t\tfmt.Printf(\"%s is not writable by current user; write will fail\\n\", gitignorePath)\n\t}\n} else if fi, err := os.Stat(filepath.Dir(gitignorePath)); err == nil && fi.Mode().Perm()&0200 == 0 {\n\tfmt.Printf(\"directory %s is not writable; write will fail\\n\", filepath.Dir(gitignorePath))\n}","typeGuard":null,"tryCatchPattern":"err := applyStealthFixes(...)\nvar perr *fs.PathError\nif errors.As(err, &perr) && errors.Is(perr.Err, syscall.ENOSPC) {\n\tfmt.Println(\"disk full while writing .gitignore; free space and retry\")\n} else if errors.As(err, &perr) && errors.Is(perr.Err, syscall.EACCES) {\n\tfmt.Printf(\"permission denied writing %s; chown/chmod the file: %v\\n\", gitignorePath, err)\n}","preventionTips":["Keep .gitignore owned by the running user with mode 0644.","Monitor disk space/quotas on dev machines and CI runners.","Pause file-sync/AV tools that briefly lock repo files during writes.","Check SELinux/AppArmor policies if writes to project dirs are consistently denied."],"tags":["git","gitignore","file-write","permissions","cli"],"backgroundTag":"file-write-permission-denied","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}