{"record":{"id":"10b479c8e25dc0dc","repo":"gastownhall/beads","slug":"failed-to-create-hooks-directory-w","errorCode":null,"errorMessage":"failed to create hooks directory: %w","messagePattern":"failed to create hooks directory: %w","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/bd/hooks.go","lineNumber":902,"sourceCode":"\t\t}\n\t} else {\n\t\t// Use common git directory for hooks (shared across worktrees)\n\t\tvar err error\n\t\thooksDir, err = git.GetGitHooksDir()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// Create hooks directory if it doesn't exist.\n\t// Directories inside .beads/ use BeadsDirPerm (0700); git-managed hook\n\t// dirs (.git/hooks, .beads-hooks) use 0755 so git can execute them.\n\thooksDirPerm := os.FileMode(0755)\n\tif beadsHooks {\n\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","sourceCodeStart":884,"sourceCodeEnd":920,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/cmd/bd/hooks.go#L884-L920","documentation":"bd could not create the hooks directory (e.g. .git/hooks, .beads/hooks, or .beads-hooks) via os.MkdirAll, so hook installation aborts. The underlying OS error is wrapped (%w), so the real cause — permissions, a file blocking the path, read-only filesystem — is in the chained message.","triggerScenarios":"installHooksWithOptions calls os.MkdirAll(hooksDir, perm) and the OS returns an error: parent dir not writable, a regular file already exists at hooksDir, full disk, or read-only mount (e.g. container image filesystem).","commonSituations":"A stray file named `hooks` exists inside .beads/ or .git/; running bd as a non-root user in a root-owned checkout; Docker/npm-ci environments mounting the repo read-only.","solutions":["Read the wrapped OS error to identify the cause (permission denied vs file exists vs read-only)","Remove/rename any regular file occupying the hooks directory path, then re-run","Fix directory ownership/permissions (e.g. `sudo chown -R $(whoami) .git .beads`) and re-run","If the filesystem is read-only, install hooks from a writable checkout"],"exampleFix":"// before: file blocks directory\n$ ls .beads/hooks  # -rw-r--r-- regular file\n// after\n$ mv .beads/hooks .beads/hooks.old && bd hooks install","handlingStrategy":"validation","validationCode":"dir=\"$(dirname \"$target\")\"; if [ -e \"$dir\" ] && [ ! -d \"$dir\" ]; then echo \"$dir exists and is not a directory\"; fi; [ -w \"$dir\" ] || echo \"$dir not writable\"","typeGuard":null,"tryCatchPattern":"if err := installHooks(...); err != nil { var pe *fs.PathError; if errors.As(err, &pe) && errors.Is(pe.Err, syscall.EACCES) { /* fix permissions and retry */ } }","preventionTips":["Check hooks directory permissions before scripted installs","Ensure no regular file occupies the hooks directory path","Run bd as the same user that owns the checkout"],"tags":["filesystem","permissions","hooks"],"backgroundTag":"mkdir-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}