gastownhall/beads · error

failed to write .gitignore: %w

Error message

failed to write .gitignore: %w

What it means

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.

Source

Thrown at cmd/bd/init_stealth.go:221

		}
		out = append(out, lines[i])
	}
	if !changed {
		return false, nil
	}

	newContent := strings.Join(out, "\n")
	if strings.TrimSpace(newContent) == "" {
		// beads was the only reason this .gitignore existed — remove it for true stealth.
		if err := os.Remove(gitignorePath); err != nil {
			return false, fmt.Errorf("failed to remove emptied .gitignore: %w", err)
		}
		return true, nil
	}

	// #nosec G306 - gitignore needs to be readable by git and collaborators
	if err := os.WriteFile(gitignorePath, []byte(newContent), 0644); err != nil {
		return false, fmt.Errorf("failed to write .gitignore: %w", err)
	}
	return true, nil
}

// isStealthRepo reports whether beads must keep its footprint out of tracked git files for the
// workspace at repoPath. It keys off the persisted no-git-ops flag — the same signal bd prime uses
// for the stealth session-close protocol (GH#593). bd init --stealth sets it, and a user may also
// set it directly; either way beads routes ignores into .git/info/exclude rather than a tracked
// .gitignore.
func isStealthRepo(repoPath string) bool {
	beadsDir := doctor.ResolveBeadsDirForRepo(repoPath)
	return config.GetStringFromDir(beadsDir, "no-git-ops") == "true"
}

// trackedGitignoreHasBeadsSection reports whether the tracked project-root .gitignore at repoPath
// still carries the bd-managed section header — i.e. a previous run leaked Dolt patterns into a
// git-visible file. Used by the stealth doctor check to flag the leak for --fix to clean up.
func trackedGitignoreHasBeadsSection(repoPath string) bool {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Fix ownership/permissions: sudo chown $(whoami) .gitignore && chmod 644 .gitignore, and ensure the repo directory is writable (chmod u+w).
  2. Free disk space / check quota if the error wraps ENOSPC (df -h).
  3. If the mount is read-only, remount or clone the repo into a writable path.
  4. Disable or pause file-sync/AV tools that lock .gitignore, then retry the fix.
  5. Manually edit .gitignore to remove the beads section, then re-run bd stealth fix to verify.

Example fix

// before (file owned by root)
-rw-r--r-- 1 root root 512 .gitignore

// after
$ sudo chown $(whoami) .gitignore && chmod 644 .gitignore
$ bd stealth fix  # os.WriteFile succeeds
Defensive patterns

Strategy: try-catch

Validate before calling

if info, err := os.Stat(gitignorePath); err == nil {
	if info.Mode().Perm()&0200 == 0 {
		fmt.Printf("%s is not writable by current user; write will fail\n", gitignorePath)
	}
} else if fi, err := os.Stat(filepath.Dir(gitignorePath)); err == nil && fi.Mode().Perm()&0200 == 0 {
	fmt.Printf("directory %s is not writable; write will fail\n", filepath.Dir(gitignorePath))
}

Try / catch

err := applyStealthFixes(...)
var perr *fs.PathError
if errors.As(err, &perr) && errors.Is(perr.Err, syscall.ENOSPC) {
	fmt.Println("disk full while writing .gitignore; free space and retry")
} else if errors.As(err, &perr) && errors.Is(perr.Err, syscall.EACCES) {
	fmt.Printf("permission denied writing %s; chown/chmod the file: %v\n", gitignorePath, err)
}

Prevention

When it happens

Trigger: 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.

Common situations: .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.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/bdde1f5430cc243e. Report an issue: GitHub.