{"record":{"id":"7e90d6cec624f1e8","repo":"larksuite/cli","slug":"s-q-hookname-must-match-a-z0-9-a-z0-9","errorCode":null,"errorMessage":"%s %q: hookName must match ^[a-z0-9][a-z0-9-]*$","messagePattern":"(.+?) %q: hookName must match \\^\\[a-z0-9\\]\\[a-z0-9-\\]\\*\\$","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"extension/platform/builder.go","lineNumber":239,"sourceCode":"//\n//\tfunc init() { platform.Register(platform.NewPlugin(...).MustBuild()) }\n//\n// A panic in init runs before the framework's recover guard is\n// installed and will crash the binary. That is the intended\n// 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)","sourceCodeStart":221,"sourceCodeEnd":257,"githubUrl":"https://github.com/larksuite/cli/blob/7fd6ef3c07182257ce776cdc5a614e122d5bd4b3/extension/platform/builder.go#L221-L257","documentation":"validateHookName enforces that every hook registered on a plugin Builder matches the same grammar as plugin names: ^[a-z0-9][a-z0-9-]*$. The rejected action is skipped and the error is recorded on the Builder, surfaced when the plugin is built.","triggerScenarios":"Calling Observer, Wrap, or On with a hookName containing uppercase letters, underscores, or starting with a hyphen, e.g. b.On(\"onMessage_received\", ...).","commonSituations":"Using Go-style or event-style names (snake_case, camelCase) for hooks; copying event type names that contain dots or underscores into hook names.","solutions":["Rename the hook to lowercase kebab-case, e.g. \"message-received\"","Ensure the hook name starts with [a-z0-9], not '-' or an uppercase letter","If the hook name derives from an external event type, map it to a canonical kebab-case name first","After fixing, rebuild the plugin and confirm no errs accumulate"],"exampleFix":"// before\nb.On(\"onMessage_received\", handler)\n// after\nb.On(\"message-received\", handler)","handlingStrategy":"validation","validationCode":"var hookNameRe = regexp.MustCompile(`^[a-z0-9][a-z0-9-]*$`)\nfunc validHook(n string) bool { return hookNameRe.MatchString(n) }","typeGuard":null,"tryCatchPattern":"b.On(hook, handler) // registration with invalid name is skipped; check after:\nfor _, e := range b.Errs() { log.Printf(\"plugin build: %v\", e) }","preventionTips":["Centralize hook-name generation in one kebab-case helper","Table-test all hook names against the regex","Map external event names to canonical hook names explicitly","Don't paste event type strings (with dots/underscores) as hook names"],"tags":["plugin","hook","naming","validation"],"backgroundTag":"invalid-identifier-format","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"}