gastownhall/beads · error
hook migration blocked by %d issue(s): %s
Error message
hook migration blocked by %d issue(s): %s
What it means
applyHookMigrationExecution refuses to run any writes when the execution plan carries BlockingErrors collected during planning (e.g. symlink hazards, collisions). All blockers are joined into one error so the user sees the full list in a single run rather than fixing them one at a time.
Source
Thrown at cmd/bd/migrate_hooks_apply.go:237
case "custom_with_sidecars":
return hookMigrationWriteFromHookFile, hook.HookPath, nil
default:
if hook.NeedsMigration {
return "", "", fmt.Errorf("%s has unsupported migration state %q", hook.Name, hook.State)
}
return "", "", fmt.Errorf("%s does not require migration", hook.Name)
}
}
type preparedHookWrite struct {
HookName string
Path string
Content []byte
}
func applyHookMigrationExecution(execPlan hookMigrationExecutionPlan) (hookMigrationApplySummary, error) {
if len(execPlan.BlockingErrors) > 0 {
return hookMigrationApplySummary{}, fmt.Errorf(
"hook migration blocked by %d issue(s): %s",
len(execPlan.BlockingErrors),
strings.Join(execPlan.BlockingErrors, "; "),
)
}
preparedWrites, err := prepareHookMigrationWrites(execPlan.WriteOps)
if err != nil {
return hookMigrationApplySummary{}, err
}
if err := validateRetireCollisionPolicy(execPlan.RetireOps); err != nil {
return hookMigrationApplySummary{}, err
}
summary := hookMigrationApplySummary{
WrittenHooks: make([]string, 0, len(preparedWrites)),
RetiredArtifacts: make([]string, 0, len(execPlan.RetireOps)),View on GitHub (pinned to 71377f2769)
Solutions
- Read the listed issues after the colon and fix each (replace symlinks with real files, remove colliding files)
- Re-run with the plan/dry-run command to confirm blockers are gone before applying
- Re-run `bd migrate hooks apply` after fixes — the error is a pre-flight gate, not corruption
- Never bypass the guard; replace symlinked hook paths with real files
Example fix
// before (dotfiles symlink) .git/hooks/pre-commit -> ~/dotfiles/pre-commit // after (real file) cp ~/dotfiles/pre-commit .git/hooks/pre-commit && rm .git/hooks/pre-commit && mv pre-commit .git/hooks/pre-commit
Defensive patterns
Strategy: validation
Validate before calling
// dry-run the plan and assert no blockers before apply
const plan = buildPlan();
if (plan.BlockingErrors.length > 0) { console.error(plan.BlockingErrors); process.exit(1); } Try / catch
summary, err := applyHookMigrationExecution(plan)
if err != nil && strings.HasPrefix(err.Error(), "hook migration blocked by") {
for _, b := range plan.BlockingErrors { fix(b) }
// retry once
} Prevention
- Never symlink .git/hooks (dotfile managers: use real files or copy)
- Run dry-run before every apply
- Fix all listed blockers in one pass — they're all reported together
When it happens
Trigger: buildHookMigrationExecutionPlan appended entries to execPlan.BlockingErrors (unsafe paths, collisions, unsupported states) and apply was invoked anyway via maybeApplyHookMigration / fixPendingMigrations.
Common situations: Hook paths are symlinks (common with dotfile managers like stow/chezmoi); destination files created by other tools between plan and apply; partially migrated repos with leftover sidecars.
Related errors
- hook migration is blocked: - %s
- %s has unsupported migration state %q
- %s does not require migration
- reading source content for %s from %s: %w
- unknown source kind %q for %s
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/42f810d72dea1f63.
Report an issue: GitHub.