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

  1. Read the listed issues after the colon and fix each (replace symlinks with real files, remove colliding files)
  2. Re-run with the plan/dry-run command to confirm blockers are gone before applying
  3. Re-run `bd migrate hooks apply` after fixes — the error is a pre-flight gate, not corruption
  4. 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

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


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/42f810d72dea1f63. Report an issue: GitHub.