{"record":{"id":"34b0d1376dbd5a12","repo":"gastownhall/beads","slug":"failed-to-remove-s-w-34b0d1","errorCode":null,"errorMessage":"failed to remove %s: %w","messagePattern":"failed to remove (.+?): %w","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/bd/hooks.go","lineNumber":1348,"sourceCode":"\t\thookPath := filepath.Join(hooksDir, hookName)\n\n\t\t// #nosec G304 -- hook path constrained to .git/hooks directory\n\t\tcontent, err := os.ReadFile(hookPath)\n\t\tif err != nil {\n\t\t\tif os.IsNotExist(err) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn fmt.Errorf(\"failed to read %s: %w\", hookName, err)\n\t\t}\n\n\t\t// Try to remove only the beads section (GH#1380)\n\t\tnewContent, found := removeHookSection(string(content))\n\t\tif found {\n\t\t\tremaining := strings.TrimSpace(newContent)\n\t\t\tif remaining == \"\" || remaining == \"#!/usr/bin/env sh\" || remaining == \"#!/bin/sh\" {\n\t\t\t\t// Only shebang left — remove the file entirely\n\t\t\t\tif err := os.Remove(hookPath); err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"failed to remove %s: %w\", hookName, err)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// #nosec G306 -- git hooks must be executable\n\t\t\t\tif err := os.WriteFile(hookPath, []byte(newContent), 0755); err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"failed to write %s: %w\", hookName, err)\n\t\t\t\t}\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\t// No section markers — check if it's a legacy bd hook (remove entire file)\n\t\tversionInfo, verr := getHookVersion(hookPath)\n\t\tif verr == nil && versionInfo.IsBdHook {\n\t\t\tif err := os.Remove(hookPath); err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to remove %s: %w\", hookName, err)\n\t\t\t}\n\t\t\t// Restore backup if exists\n\t\t\tbackupPath := hookPath + \".backup\"","sourceCodeStart":1330,"sourceCodeEnd":1366,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/cmd/bd/hooks.go#L1330-L1366","documentation":"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.","triggerScenarios":"`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).","commonSituations":"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.","solutions":["Delete the hook manually: `rm -f .git/hooks/<hookName>` (with sudo if owned by root).","Check for the immutable flag: `lsattr <hookfile>` and `chattr -i` if set.","Ensure the .git/hooks directory is writable.","Retry `bd hooks uninstall`."],"exampleFix":"// before\n$ bd hooks uninstall\n// error: failed to remove pre-commit: remove .../pre-commit: permission denied\n// after\n$ sudo rm -f .git/hooks/pre-commit\n$ bd hooks uninstall","handlingStrategy":"validation","validationCode":"hooksDir, _ := git.GetGitHooksDir()\nif !isDirWritable(hooksDir) {\n    return errors.New(\".git/hooks directory is not writable; fix permissions before uninstall\")\n}","typeGuard":null,"tryCatchPattern":"if err := uninstallHooks(); err != nil {\n    if strings.Contains(err.Error(), \"failed to remove\") {\n        log.Printf(\"remove the hook manually and/or restore its .backup, then retry: %v\", err)\n    }\n    return err\n}","preventionTips":["Ensure the .git/hooks directory is writable by the uninstalling user","Do not set immutable flags (chattr +i) on hook files","On Windows, close editors/AV holding hook files open"],"tags":["git-hooks","file-permissions","uninstall","filesystem"],"backgroundTag":"file-delete-permission-denied","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}