gastownhall/beads · error
failed to stat %s: %w
Error message
failed to stat %s: %w
What it means
Wraps an os.Lstat failure in guardHookWritePath when the hook path exists but cannot be stat'd for a reason other than not-exist. guardHookWritePath pre-checks the target before bd writes a hook so it never clobbers symlinks or foreign tracked files.
Source
Thrown at cmd/bd/hooks.go:818
return nil
},
}
// guardHookWritePath refuses hook writes that would silently modify files
// bd does not own (bd-5vdt8):
// - a symlinked hook path: os.WriteFile follows the link (O_TRUNC) and
// rewrites the target inode — e.g. a repo-tracked script the hook
// points at, dirtying every clone that shares it
// - a hook file tracked by git: writing it dirties the working tree.
// allowTracked exempts shared installs (.beads-hooks/ is deliberately
// committed).
func guardHookWritePath(hookPath string, allowTracked bool) error {
fi, err := os.Lstat(hookPath)
if err != nil {
if os.IsNotExist(err) {
return nil // creating a new file — nothing to clobber
}
return fmt.Errorf("failed to stat %s: %w", hookPath, err)
}
if fi.Mode()&os.ModeSymlink != 0 {
target := "unresolvable target"
if resolved, rerr := filepath.EvalSymlinks(hookPath); rerr == nil {
target = resolved
} else if link, lerr := os.Readlink(hookPath); lerr == nil {
target = link
}
return fmt.Errorf("%s is a symlink to %s; writing would rewrite the link target, not the hook\nRemove the symlink (or leave that hook to its owner) and re-run", hookPath, target)
}
if allowTracked {
return nil
}
// A tracked file is refused only when bd does NOT own it: writing into a
// foreign tracked file dirties every clone that shares it (the wy-81fnur
// incident). A bd-owned hook the user chose to commit (e.g. a team-shared
// .beads/hooks/) is bd's to maintain — same policy as shared installs.
if isGitTrackedFile(hookPath) && !isBdOwnedHookFile(hookPath) {View on GitHub (pinned to 71377f2769)
Solutions
- Fix permissions on the .git/hooks directory (chmod u+rx .git/hooks) and retry.
- Check the wrapped cause (%w) for the exact errno and address it (ownership, ACL, mount).
- Run outside restricted sandboxes or grant the process access to the repo's .git directory.
Example fix
// before $ bd hooks install // error: failed to stat .git/hooks/pre-commit: permission denied // after $ chmod u+rx .git/hooks $ bd hooks install
Defensive patterns
Strategy: validation
Validate before calling
if fi, err := os.Lstat(hookPath); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("cannot access hook path %s: %w", hookPath, err)
} Try / catch
if err := guardHookWritePath(hookPath, allowTracked); err != nil {
if strings.Contains(err.Error(), "failed to stat") {
// fix permissions or abort install with guidance
}
} Prevention
- Ensure .git and .git/hooks are accessible (u+rx) to the user running bd.
- Run hooks installation outside restricted sandboxes/containers.
- Check the wrapped errno in the cause to target the fix.
When it happens
Trigger: installHooksWithOptions or applyHookMigrationExecution calls guardHookWritePath(hookPath, allowTracked); os.Lstat fails with an error where os.IsNotExist(err) is false (e.g. permission denied on a parent directory).
Common situations: Parent .git/hooks directory lacks execute/read permission, sandboxed environment blocking stat, filesystem error, or a path component that is inaccessible.
Related errors
- reading hook file: %w
- writing migrated hook %s: %w
- checking source sidecar %s: %w
- uow: creating server root directory: %w
- dolt path is not executable
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/57d28b69c1cfdd4e.
Report an issue: GitHub.