{"record":{"id":"55995402cfcecce2","repo":"sipeed/picoclaw","slug":"events-handler-is-nil","errorCode":null,"errorMessage":"events: handler is nil","messagePattern":"events: handler is nil","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/events/subscription.go","lineNumber":19,"sourceCode":"package events\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"log\"\n\t\"runtime/debug\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n)\n\nconst defaultSubscriberBuffer = 16\n\nvar (\n\t// ErrBusClosed is returned when subscribing to a closed event bus.\n\tErrBusClosed = errors.New(\"events: bus is closed\")\n\t// ErrNilHandler is returned when subscribing without a handler.\n\tErrNilHandler = errors.New(\"events: handler is nil\")\n)\n\n// Handler processes a runtime event delivered to a subscription.\ntype Handler func(context.Context, Event) error\n\n// SubscribeOptions controls how a subscription receives events.\ntype SubscribeOptions struct {\n\tName         string\n\tBuffer       int\n\tPriority     int\n\tConcurrency  ConcurrencyKind\n\tBackpressure BackpressurePolicy\n\t// Timeout bounds how long the subscription worker waits for one handler call.\n\t// Handlers should still honor ctx cancellation; timed-out calls keep running\n\t// until their handler returns.\n\tTimeout     time.Duration\n\tPanicPolicy PanicPolicy\n}","sourceCodeStart":1,"sourceCodeEnd":37,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/events/subscription.go#L1-L37","documentation":"Returned by EventChannel.Subscribe and EventChannel.SubscribeOnce (channel.go:55,72) when the handler argument is nil. The channel-based variant SubscribeChan takes no handler and is the intended API when you want events on a <-chan Event instead of a callback.","triggerScenarios":"Passing a nil events.Handler (e.g. a func variable that was never assigned, or a conditional handler that evaluated to nil) to Subscribe or SubscribeOnce.","commonSituations":"Handler selected by a map/switch that misses a case and leaves the variable nil; refactoring a SubscribeChan call to Subscribe and forgetting to pass the handler; tests registering a stub that is nil.","solutions":["Pass a non-nil events.Handler callback to Subscribe/SubscribeOnce","If you want events on a channel, call SubscribeChan(ctx, opts) which returns (Subscription, <-chan Event, error) and requires no handler","Guard before subscribing: if handler == nil { return errors.New(\"handler required\") } at the call site"],"exampleFix":"// before\nvar h events.Handler // nil\nsub, err := ch.Subscribe(ctx, opts, h)\n\n// after\nsub, eventsCh, err := ch.SubscribeChan(ctx, opts) // channel-based, no handler\n// or: h := func(ctx context.Context, e events.Event) error { return nil }","handlingStrategy":"validation","validationCode":"if handler == nil {\n    return fmt.Errorf(\"events handler required for Subscribe\")\n}\nsub, err := ch.Subscribe(ctx, opts, handler)","typeGuard":null,"tryCatchPattern":"sub, err := ch.Subscribe(ctx, opts, handler)\nif err != nil && errors.Is(err, events.ErrNilHandler) {\n    // programmer error: fix the call site; consider falling back to SubscribeChan\n}","preventionTips":["Use SubscribeChan when you want a channel instead of passing nil","Initialize handler variables at declaration; avoid conditional assignment that can leave them nil","In tests, always pass a stub handler func"],"tags":["events","validation","api-misuse"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}