{"record":{"id":"42f810d72dea1f63","repo":"gastownhall/beads","slug":"hook-migration-blocked-by-d-issue-s-s","errorCode":null,"errorMessage":"hook migration blocked by %d issue(s): %s","messagePattern":"hook migration blocked by (.+?) issue\\(s\\): (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/bd/migrate_hooks_apply.go","lineNumber":237,"sourceCode":"\tcase \"custom_with_sidecars\":\n\t\treturn hookMigrationWriteFromHookFile, hook.HookPath, nil\n\tdefault:\n\t\tif hook.NeedsMigration {\n\t\t\treturn \"\", \"\", fmt.Errorf(\"%s has unsupported migration state %q\", hook.Name, hook.State)\n\t\t}\n\t\treturn \"\", \"\", fmt.Errorf(\"%s does not require migration\", hook.Name)\n\t}\n}\n\ntype preparedHookWrite struct {\n\tHookName string\n\tPath     string\n\tContent  []byte\n}\n\nfunc applyHookMigrationExecution(execPlan hookMigrationExecutionPlan) (hookMigrationApplySummary, error) {\n\tif len(execPlan.BlockingErrors) > 0 {\n\t\treturn hookMigrationApplySummary{}, fmt.Errorf(\n\t\t\t\"hook migration blocked by %d issue(s): %s\",\n\t\t\tlen(execPlan.BlockingErrors),\n\t\t\tstrings.Join(execPlan.BlockingErrors, \"; \"),\n\t\t)\n\t}\n\n\tpreparedWrites, err := prepareHookMigrationWrites(execPlan.WriteOps)\n\tif err != nil {\n\t\treturn hookMigrationApplySummary{}, err\n\t}\n\n\tif err := validateRetireCollisionPolicy(execPlan.RetireOps); err != nil {\n\t\treturn hookMigrationApplySummary{}, err\n\t}\n\n\tsummary := hookMigrationApplySummary{\n\t\tWrittenHooks:     make([]string, 0, len(preparedWrites)),\n\t\tRetiredArtifacts: make([]string, 0, len(execPlan.RetireOps)),","sourceCodeStart":219,"sourceCodeEnd":255,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/cmd/bd/migrate_hooks_apply.go#L219-L255","documentation":"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.","triggerScenarios":"buildHookMigrationExecutionPlan appended entries to execPlan.BlockingErrors (unsafe paths, collisions, unsupported states) and apply was invoked anyway via maybeApplyHookMigration / fixPendingMigrations.","commonSituations":"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.","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"],"exampleFix":"// before (dotfiles symlink)\n.git/hooks/pre-commit -> ~/dotfiles/pre-commit\n// after (real file)\ncp ~/dotfiles/pre-commit .git/hooks/pre-commit && rm .git/hooks/pre-commit && mv pre-commit .git/hooks/pre-commit","handlingStrategy":"validation","validationCode":"// dry-run the plan and assert no blockers before apply\nconst plan = buildPlan();\nif (plan.BlockingErrors.length > 0) { console.error(plan.BlockingErrors); process.exit(1); }","typeGuard":null,"tryCatchPattern":"summary, err := applyHookMigrationExecution(plan)\nif err != nil && strings.HasPrefix(err.Error(), \"hook migration blocked by\") {\n    for _, b := range plan.BlockingErrors { fix(b) }\n    // retry once\n}","preventionTips":["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"],"tags":["migration","git-hooks","safety-gate","blocked"],"backgroundTag":"migration-blocked","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}