{"record":{"id":"de192917322800e4","repo":"gastownhall/beads","slug":"refusing-to-install-s-hook-w","errorCode":null,"errorMessage":"refusing to install %s hook: %w","messagePattern":"refusing to install (.+?) hook: %w","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/bd/hooks.go","lineNumber":917,"sourceCode":"\t\thooksDirPerm = config.BeadsDirPerm\n\t}\n\tif err := os.MkdirAll(hooksDir, hooksDirPerm); err != nil {\n\t\treturn fmt.Errorf(\"failed to create hooks directory: %w\", err)\n\t}\n\n\t// When setting a local core.hooksPath (beads or shared mode), preserve any\n\t// hooks from the previously effective hooks directory (e.g. a global\n\t// core.hooksPath or the default .git/hooks). Without this, setting a local\n\t// core.hooksPath silently shadows the global one and those hooks stop running.\n\tif beadsHooks || shared {\n\t\tpreservePreexistingHooks(hooksDir)\n\t}\n\n\t// Refuse the whole install up front if any target is unsafe to write —\n\t// stopping midway through the loop would leave hooks half-installed.\n\tfor _, hookName := range hookNames {\n\t\tif err := guardHookWritePath(filepath.Join(hooksDir, hookName), shared); err != nil {\n\t\t\treturn fmt.Errorf(\"refusing to install %s hook: %w\", hookName, err)\n\t\t}\n\t}\n\n\t// Install each hook using section markers (GH#1380).\n\t// Only the content between markers is managed by beads; user content\n\t// outside the markers is preserved across reinstalls and upgrades.\n\tfor _, hookName := range hookNames {\n\t\thookPath := filepath.Join(hooksDir, hookName)\n\t\tsection := generateHookSection(hookName)\n\n\t\t// Read existing hook file (if any)\n\t\t// #nosec G304 -- hook path constrained to hooks directory\n\t\texisting, readErr := os.ReadFile(hookPath)\n\n\t\tif readErr != nil && !os.IsNotExist(readErr) {\n\t\t\treturn fmt.Errorf(\"failed to read %s: %w\", hookName, readErr)\n\t\t}\n","sourceCodeStart":899,"sourceCodeEnd":935,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/cmd/bd/hooks.go#L899-L935","documentation":"installHooksWithOptions pre-flights every target hook through guardHookWritePath and refuses the entire install if any is unsafe (symlinked, git-tracked and foreign, etc.), so it never leaves hooks half-installed. This error wraps one of those guard errors — notably error 750's tracked-hook refusal or the symlink refusal.","triggerScenarios":"`bd hooks install` where any target (pre-commit, pre-push, ...) resolves to a symlink, or to a git-tracked file that is not bd-owned.","commonSituations":" frameworks like husky symlink .git/hooks into their own directory; a custom hook was committed to git; switching hook managers without cleaning up old symlinks.","solutions":["Read the wrapped (%w) cause to see whether it is a symlink or tracked-file refusal","If symlinked (e.g. by husky): remove the symlink or configure bd's hooks directory (.beads-hooks / .beads/hooks) so it does not clash with the other tool","If git-tracked: `git rm --cached <hook>` or move hooks to an untracked directory, then re-run","Re-run `bd hooks install` once all targets are safe"],"exampleFix":"// before: husky symlink\n$ ls -l .git/hooks/pre-commit -> ../../.husky/pre-commit\n// after\n$ rm .git/hooks/pre-commit && bd hooks install","handlingStrategy":"validation","validationCode":"for h in pre-commit pre-push; do p=\".git/hooks/$h\"; [ -L \"$p\" ] && echo \"$p is a symlink\"; git ls-files --error-unmatch \"$p\" 2>/dev/null && echo \"$p is tracked\"; done","typeGuard":"func canInstallHook(path string) bool { fi, err := os.Lstat(path); if err != nil { return true }; return fi.Mode()&os.ModeSymlink == 0 && (!isGitTrackedFile(path) || isBdOwnedHookFile(path)) }","tryCatchPattern":"if err := installHooksWithOptions(names, force, shared, chain, beads); err != nil { if strings.Contains(err.Error(), \"refusing to install\") { log.Warn(\"hook target unsafe; resolve wrapped cause first\", \"cause\", err) }; return err }","preventionTips":["Remove husky/other-manager symlinks before bd installs, or point bd at its own hooks dir","Don't commit hook files bd will write","Pre-run guardHookWritePath logic (or `bd hooks install --dry-run` if available) in scripts"],"tags":["git","hooks","safety-guard"],"backgroundTag":"hook-install-refused","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}