gastownhall/beads · error
%s has unsupported migration state %q
Error message
%s has unsupported migration state %q
What it means
chooseHookMigrationWriteSource maps a git hook's detected migration State to a write-source strategy (template, hook file, old sidecar, backup sidecar). This error is thrown in the default case when the hook reports NeedsMigration=true but its State string is not one of the recognized states, meaning the planner has no strategy for how to produce the migrated content.
Source
Thrown at cmd/bd/migrate_hooks_apply.go:223
if hook.ReadError != "" {
return fmt.Sprintf("%s (%s): %s", hook.Name, hook.State, hook.ReadError)
}
return fmt.Sprintf("%s (%s): %s", hook.Name, hook.State, suggestion)
}
func chooseHookMigrationWriteSource(hook doctor.HookMigrationHookPlan) (hookMigrationWriteSource, string, error) {
switch hook.State {
case "legacy_only":
return hookMigrationWriteFromTemplate, "", nil
case "legacy_with_old_sidecar", "legacy_with_both_sidecars", "missing_with_old_sidecar", "missing_with_both_sidecars":
return hookMigrationWriteFromOld, hook.HookPath + ".old", nil
case "legacy_with_backup_sidecar", "missing_with_backup_sidecar":
return hookMigrationWriteFromBackup, hook.HookPath + ".backup", nil
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, "; "),
)View on GitHub (pinned to 71377f2769)
Solutions
- Re-run detection (`bd migrate hooks` plan/dry-run) so state is recomputed with the current binary
- Update bd to the latest version so detector and planner state vocabularies match
- Inspect the hook directory for unexpected files (.old/.backup sidecars) and remove stale ones
- If persistently stuck, delete the hook and let migration install it fresh from the template
Defensive patterns
Strategy: validation
Validate before calling
// before apply, inspect the plan
const knownStates = new Set(['legacy','legacy_with_old','legacy_with_backup_sidecar','legacy_with_both_sidecars','missing_with_backup_sidecar','custom_with_sidecars']);
if (hook.NeedsMigration && !knownStates.has(hook.State)) throw new Error(`unsupported state ${hook.State}`); Try / catch
if err := applyMigration(hook); err != nil && strings.Contains(err.Error(), "unsupported migration state") {
// fall back to full re-install from template
} Prevention
- Run the plan/dry-run first and review states
- Keep bd updated so detector and planner agree
- Clean stale sidecar files so state detection stays unambiguous
When it happens
Trigger: A hook whose State (computed by the migration detector) is an unrecognized value while NeedsMigration is true — i.e. detection marked the hook dirty but produced a state label the apply planner doesn't handle.
Common situations: A bd version mismatch where the detector writes new state labels the older apply code doesn't know; manually edited hooks producing a hybrid state; corrupted or foreign sidecar files confusing state detection.
Related errors
- %s does not require migration
- hook migration blocked by %d issue(s): %s
- reading source content for %s from %s: %w
- unknown source kind %q for %s
- clone from %s succeeded, but the database needs %d schema %s
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/faffc3df720c8cb6.
Report an issue: GitHub.