gastownhall/beads · error
%s is a symlink to %s; writing would rewrite the link target
Error message
%s is a symlink to %s; writing would rewrite the link target, not the hook Remove the symlink (or leave that hook to its owner) and re-run
What it means
Returned by guardHookWritePath when the hook path is a symlink: writing to it would overwrite the link's target file (owned by whoever set up the link) rather than the hook itself, so bd refuses. This protects foreign tools (e.g. other hook managers) that own the real file.
Source
Thrown at cmd/bd/hooks.go:827
// - 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) {
return fmt.Errorf("%s is tracked by git and not a bd-managed hook; bd will not modify committed files it does not own\nUntrack it (git rm --cached) or move hooks to an untracked directory and re-run", hookPath)
}
return nil
}
// isBdOwnedHookFile reports whether the hook file at path is bd-managed:
// either section-marker format or a legacy bd hook (shim or inline).
func isBdOwnedHookFile(path string) bool {
content, err := os.ReadFile(path) // #nosec G304 -- path is a hook location bd resolvedView on GitHub (pinned to 71377f2769)
Solutions
- Remove the symlink (rm .git/hooks/<hook>) and re-run bd init / bd hooks install so bd owns a real file.
- Leave that particular hook to its owner (skip installing bd's hook there) if another tool manages it.
- Configure the other hook manager to chain to bd's hook instead of linking the file.
Example fix
// before $ ls -l .git/hooks/pre-commit -> /dotfiles/pre-commit $ bd hooks install // error: .git/hooks/pre-commit is a symlink to /dotfiles/pre-commit ... // after $ rm -f .git/hooks/pre-commit $ bd hooks install
Defensive patterns
Strategy: validation
Validate before calling
if fi, err := os.Lstat(hookPath); err == nil && fi.Mode()&os.ModeSymlink != 0 {
return errors.New(hookPath + " is a symlink; resolve before installing bd hooks")
} Try / catch
if err := guardHookWritePath(hookPath, allowTracked); err != nil {
if strings.Contains(err.Error(), "is a symlink to") {
// skip this hook or prompt the user to remove the link
}
} Prevention
- Check for symlinked hooks (ls -l .git/hooks) before running bd init in repos managed by husky/pre-commit/lefthook/dotfiles.
- Let the owning hook manager chain to bd's hook instead of linking files.
- Pass allowTracked only when bd genuinely owns the tracked file.
When it happens
Trigger: installHooksWithOptions or applyHookMigrationExecution finds the hook path is a symlink (Lstat ModeSymlink set); the message includes the resolved target via filepath.EvalSymlinks, or the raw link via os.Readlink if unresolvable.
Common situations: A hook manager (husky, pre-commit, lefthook) or dotfiles setup symlinked .git/hooks/* into a shared config directory; bd then tries to install its hooks over those links.
Related errors
- refusing to write migrated hook %s: %w
- ErrDependentsOutsideRequest
- resolve path: %w
- unexpected git rev-parse output
- reading hook file: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/da0bc7fc99bbeb06.
Report an issue: GitHub.