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

  1. Re-run detection (`bd migrate hooks` plan/dry-run) so state is recomputed with the current binary
  2. Update bd to the latest version so detector and planner state vocabularies match
  3. Inspect the hook directory for unexpected files (.old/.backup sidecars) and remove stale ones
  4. 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

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


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