gastownhall/beads · error

failed to remove %s: %w

Error message

failed to remove %s: %w

What it means

During uninstall, if the hook file contains a bd section whose removal leaves only a shebang (or nothing), bd deletes the whole hook file. This error wraps os.Remove failing — typically a permission problem on the file or its directory.

Source

Thrown at cmd/bd/hooks.go:1348

		hookPath := filepath.Join(hooksDir, hookName)

		// #nosec G304 -- hook path constrained to .git/hooks directory
		content, err := os.ReadFile(hookPath)
		if err != nil {
			if os.IsNotExist(err) {
				continue
			}
			return fmt.Errorf("failed to read %s: %w", hookName, err)
		}

		// Try to remove only the beads section (GH#1380)
		newContent, found := removeHookSection(string(content))
		if found {
			remaining := strings.TrimSpace(newContent)
			if remaining == "" || remaining == "#!/usr/bin/env sh" || remaining == "#!/bin/sh" {
				// Only shebang left — remove the file entirely
				if err := os.Remove(hookPath); err != nil {
					return fmt.Errorf("failed to remove %s: %w", hookName, err)
				}
			} else {
				// #nosec G306 -- git hooks must be executable
				if err := os.WriteFile(hookPath, []byte(newContent), 0755); err != nil {
					return fmt.Errorf("failed to write %s: %w", hookName, err)
				}
			}
			continue
		}

		// No section markers — check if it's a legacy bd hook (remove entire file)
		versionInfo, verr := getHookVersion(hookPath)
		if verr == nil && versionInfo.IsBdHook {
			if err := os.Remove(hookPath); err != nil {
				return fmt.Errorf("failed to remove %s: %w", hookName, err)
			}
			// Restore backup if exists
			backupPath := hookPath + ".backup"

View on GitHub (pinned to 71377f2769)

Solutions

  1. Delete the hook manually: `rm -f .git/hooks/<hookName>` (with sudo if owned by root).
  2. Check for the immutable flag: `lsattr <hookfile>` and `chattr -i` if set.
  3. Ensure the .git/hooks directory is writable.
  4. Retry `bd hooks uninstall`.

Example fix

// before
$ bd hooks uninstall
// error: failed to remove pre-commit: remove .../pre-commit: permission denied
// after
$ sudo rm -f .git/hooks/pre-commit
$ bd hooks uninstall
Defensive patterns

Strategy: validation

Validate before calling

hooksDir, _ := git.GetGitHooksDir()
if !isDirWritable(hooksDir) {
    return errors.New(".git/hooks directory is not writable; fix permissions before uninstall")
}

Try / catch

if err := uninstallHooks(); err != nil {
    if strings.Contains(err.Error(), "failed to remove") {
        log.Printf("remove the hook manually and/or restore its .backup, then retry: %v", err)
    }
    return err
}

Prevention

When it happens

Trigger: `bd hooks uninstall` -> uninstallHooks, removeHookSection found a bd section, remaining content is empty or only a shebang, and os.Remove(hookPath) fails (read-only hooks dir, file owned by another user, immutable flag, filesystem error).

Common situations: Hooks installed as root while uninstall runs as a normal user; .git/hooks on a read-only mount; chattr +i set on hook files; Windows file locks from another process.

Related errors


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