gastownhall/beads · error
failed to create hooks directory: %w
Error message
failed to create hooks directory: %w
What it means
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.
Source
Thrown at cmd/bd/hooks.go:902
}
} else {
// Use common git directory for hooks (shared across worktrees)
var err error
hooksDir, err = git.GetGitHooksDir()
if err != nil {
return err
}
}
// Create hooks directory if it doesn't exist.
// Directories inside .beads/ use BeadsDirPerm (0700); git-managed hook
// dirs (.git/hooks, .beads-hooks) use 0755 so git can execute them.
hooksDirPerm := os.FileMode(0755)
if beadsHooks {
hooksDirPerm = config.BeadsDirPerm
}
if err := os.MkdirAll(hooksDir, hooksDirPerm); err != nil {
return fmt.Errorf("failed to create hooks directory: %w", err)
}
// When setting a local core.hooksPath (beads or shared mode), preserve any
// hooks from the previously effective hooks directory (e.g. a global
// core.hooksPath or the default .git/hooks). Without this, setting a local
// core.hooksPath silently shadows the global one and those hooks stop running.
if beadsHooks || shared {
preservePreexistingHooks(hooksDir)
}
// Refuse the whole install up front if any target is unsafe to write —
// stopping midway through the loop would leave hooks half-installed.
for _, hookName := range hookNames {
if err := guardHookWritePath(filepath.Join(hooksDir, hookName), shared); err != nil {
return fmt.Errorf("refusing to install %s hook: %w", hookName, err)
}
}
View on GitHub (pinned to 71377f2769)
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
Example fix
// before: file blocks directory $ ls .beads/hooks # -rw-r--r-- regular file // after $ mv .beads/hooks .beads/hooks.old && bd hooks install
Defensive patterns
Strategy: validation
Validate before calling
dir="$(dirname "$target")"; if [ -e "$dir" ] && [ ! -d "$dir" ]; then echo "$dir exists and is not a directory"; fi; [ -w "$dir" ] || echo "$dir not writable"
Try / catch
if err := installHooks(...); err != nil { var pe *fs.PathError; if errors.As(err, &pe) && errors.Is(pe.Err, syscall.EACCES) { /* fix permissions and retry */ } } Prevention
- 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
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- failed to read %s: %w
- failed to write %s: %w
- dolt path is not executable
- failed to create backup directory: %w
- failed to create temp file: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/10b479c8e25dc0dc.
Report an issue: GitHub.