{"record":{"id":"a74dc991f8cc5f0c","repo":"larksuite/cli","slug":"s-q-hookname-already-used-in-this-plugin","errorCode":null,"errorMessage":"%s %q: hookName already used in this plugin","messagePattern":"(.+?) %q: hookName already used in this plugin","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"extension/platform/builder.go","lineNumber":244,"sourceCode":"// behaviour: a misconfigured plugin must NOT be silently registered.\nfunc (b *Builder) MustBuild() Plugin {\n\tp, err := b.Build()\n\tif err != nil {\n\t\tpanic(fmt.Sprintf(\"plugin %q: %v\", b.name, err))\n\t}\n\treturn p\n}\n\n// validateHookName checks the grammar and uniqueness; returns false\n// when the name was rejected (caller skips the action).\nfunc (b *Builder) validateHookName(hookName, kind string) bool {\n\tif !pluginNamePattern.MatchString(hookName) {\n\t\tb.errs = append(b.errs, fmt.Errorf(\n\t\t\t\"%s %q: hookName must match ^[a-z0-9][a-z0-9-]*$\", kind, hookName))\n\t\treturn false\n\t}\n\tif b.hookNames[hookName] {\n\t\tb.errs = append(b.errs, fmt.Errorf(\n\t\t\t\"%s %q: hookName already used in this plugin\", kind, hookName))\n\t\treturn false\n\t}\n\tb.hookNames[hookName] = true\n\treturn true\n}\n\n// builtPlugin is the Plugin implementation the builder emits.\ntype builtPlugin struct {\n\tname          string\n\tversion       string\n\tcaps          Capabilities\n\tactions       []func(Registrar)\n\trules         []*Rule\n\tskillsOverlay *SkillsOverlay\n}\n\nfunc (p *builtPlugin) Name() string               { return p.name }","sourceCodeStart":226,"sourceCodeEnd":262,"githubUrl":"https://github.com/larksuite/cli/blob/7fd6ef3c07182257ce776cdc5a614e122d5bd4b3/extension/platform/builder.go#L226-L262","documentation":"validateHookName rejects duplicate hook names within one plugin Builder; each hookName may be registered at most once across Observer, Wrap, and On. The duplicate action is skipped and the error is stored on the Builder.","triggerScenarios":"Registering the same hookName twice on one Builder, e.g. calling b.On(\"message-received\", h1) then b.On(\"message-received\", h2), or registering Observer and Wrap with the same name.","commonSituations":"Copy-pasted registration blocks; looping over a slice containing duplicate hook names; merging two plugin configurations into one Builder without deduplication.","solutions":["Use a unique hookName per registration; encode the purpose, e.g. \"message-received-log\" and \"message-received-metric\"","Deduplicate the slice of hook registrations before the loop","If the hook must run twice, combine the actions into one registration","Check b.hookNames/Builder errors after build to find which name collided"],"exampleFix":"// before\nb.On(\"message-received\", logHook)\nb.On(\"message-received\", metricHook)\n// after\nb.On(\"message-received-log\", logHook)\nb.On(\"message-received-metric\", metricHook)","handlingStrategy":"validation","validationCode":"seen := map[string]bool{}\nfor _, h := range hooks {\n  if seen[h.name] { t.Fatalf(\"duplicate hook %q\", h.name) }\n  seen[h.name] = true\n}","typeGuard":null,"tryCatchPattern":"// duplicates are recorded on the Builder and actions skipped:\nif errs := b.Errs(); len(errs) > 0 { return fmt.Errorf(\"plugin build failed: %v\", errs) }","preventionTips":["Deduplicate registration slices before looping","Compose multiple behaviors into one hook instead of re-registering","Assert in tests that each Builder accumulates zero errors","Generate hook names with a suffix derived from purpose"],"tags":["plugin","hook","duplicate","validation"],"backgroundTag":"duplicate-identifier-registration","analyzedSha":"7fd6ef3c07182257ce776cdc5a614e122d5bd4b3","analyzedAt":"2026-09-04T21:17:44.649Z","contentChangedAt":"2026-09-04T21:17:44.649Z","schemaVersion":2},"datasetVersion":"2026-09-12T02:17:10.037Z"}