{"record":{"id":"b20f88ac288e9adc","repo":"gastownhall/beads","slug":"unknown-source-kind-q-for-s","errorCode":null,"errorMessage":"unknown source kind %q for %s","messagePattern":"unknown source kind %q for (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/bd/migrate_hooks_apply.go","lineNumber":320,"sourceCode":"\t}\n\n\treturn prepared, nil\n}\n\nfunc renderMigratedHookContent(op hookMigrationWriteOp) ([]byte, error) {\n\tvar baseContent string\n\n\tswitch op.SourceKind {\n\tcase hookMigrationWriteFromTemplate:\n\t\tbaseContent = \"\"\n\tcase hookMigrationWriteFromHookFile, hookMigrationWriteFromOld, hookMigrationWriteFromBackup:\n\t\tcontent, err := os.ReadFile(op.SourcePath) // #nosec G304 -- source paths come from migration planner + known sidecar suffixes\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"reading source content for %s from %s: %w\", op.HookName, op.SourcePath, err)\n\t\t}\n\t\tbaseContent = string(content)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unknown source kind %q for %s\", op.SourceKind, op.HookName)\n\t}\n\n\tbaseContent = strings.ReplaceAll(baseContent, \"\\r\\n\", \"\\n\")\n\tbaseContent = ensureHookShebang(baseContent)\n\n\tcontent := injectHookSection(baseContent, generateHookSection(op.HookName))\n\tcontent = strings.ReplaceAll(content, \"\\r\\n\", \"\\n\")\n\tif !strings.HasSuffix(content, \"\\n\") {\n\t\tcontent += \"\\n\"\n\t}\n\n\treturn []byte(content), nil\n}\n\nfunc ensureHookShebang(content string) string {\n\tif strings.HasPrefix(content, \"#!\") {\n\t\treturn content\n\t}","sourceCodeStart":302,"sourceCodeEnd":338,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/cmd/bd/migrate_hooks_apply.go#L302-L338","documentation":"The SourceKind on a hook migration write op is a closed enum (template, hook file, old, backup). If renderMigratedHookContent sees any other value, it fails fast rather than guessing how to build content — this indicates an internal inconsistency between the planner and the renderer.","triggerScenarios":"A hookMigrationRetireOp/write op carries a SourceKind outside the four known constants, produced by newer planner code or a corrupted plan structure passed to prepareHookMigrationWrites.","commonSituations":"Version mismatch where planner and apply code disagree on the enum; hand-built or serialized plans modified externally; a code bug adding a new source kind without a renderer case.","solutions":["Upgrade bd so planner and renderer share the same source-kind set","Re-generate the migration plan with the current binary instead of reusing a stale one","Report a bug if it reproduces on the latest version — this is an internal invariant violation","Add a renderer case for the new SourceKind if you are developing bd and introduced a new kind"],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":"const knownKinds = new Set(['template','hook_file','old','backup']);\nif (!knownKinds.has(op.SourceKind)) throw new Error('unknown SourceKind: '+op.SourceKind);","typeGuard":"func validSourceKind(k hookMigrationSourceKind) bool { switch k { case hookMigrationWriteFromTemplate, hookMigrationWriteFromHookFile, hookMigrationWriteFromOld, hookMigrationWriteFromBackup: return true }; return false }","tryCatchPattern":"if err := apply(...); err != nil && strings.Contains(err.Error(), \"unknown source kind\") {\n    // regenerate the plan with the current binary and retry\n}","preventionTips":["Regenerate plans with the same binary version that applies them","Never hand-edit or cache migration plans across upgrades","If developing bd, add a renderer case whenever a new SourceKind is introduced"],"tags":["internal-invariant","migration","git-hooks","enum"],"backgroundTag":"unknown-source-kind","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}