{"id":"7e355419b4ba8570","repo":"go-redis/redis","slug":"redis-newclient-nil-options","errorCode":null,"errorMessage":"redis: NewClient nil options","messagePattern":"redis: NewClient nil options","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"redis.go","lineNumber":1921,"sourceCode":"\t*baseClient\n\tcmdable\n\n\t// cscLifecycleOwner keeps the canonical Client wrapper (the one whose GC\n\t// cleanup owns the drainer) reachable while a WithTimeout clone can still\n\t// serve from its cache. Nil on the canonical wrapper and on non-CSC clones.\n\tcscLifecycleOwner *Client\n\n\tautopipelinerMu     *sync.Mutex    // guards the autopipeliner fields against concurrent first-call creation\n\tautopipeliner       *AutoPipeliner // blocking face (Client.AutoPipeline)\n\tasyncAutopipeliner  *AutoPipeliner // deferred face (Client.AsyncAutoPipeline)\n\tautopipelinerClosed bool           // set by Close: refuse to resurrect a pipeliner on a closed client\n}\n\n// NewClient returns a client to the Redis Server specified by Options.\n// Passing nil Options will cause a panic.\nfunc NewClient(opt *Options) *Client {\n\tif opt == nil {\n\t\tpanic(\"redis: NewClient nil options\")\n\t}\n\t// clone to not share options with the caller\n\topt = opt.clone()\n\topt.init()\n\n\t// Push notifications are always enabled for RESP3 (cannot be disabled)\n\n\tc := Client{\n\t\tbaseClient: &baseClient{\n\t\t\tapClosed: &atomic.Bool{},\n\t\t\topt:      opt,\n\t\t\tonClose:  &onCloseHooks{},\n\t\t\thimport:  newHImportRegistry(),\n\t\t},\n\t}\n\tc.init()\n\n\t// Initialize push notification processor using shared helper","sourceCodeStart":1903,"sourceCodeEnd":1939,"githubUrl":"https://github.com/go-redis/redis/blob/36d97525cd8076aed67cddf54778e9ea84550929/redis.go#L1903-L1939","documentation":"NewClient panics with 'redis: NewClient nil options' when the passed *Options pointer is nil. The constructor deliberately panics (documented at redis.go:1918) rather than returning an error because a client with no options has no Addr, no pool, and cannot function - it is a programmer error, not a runtime condition. The panic surfaces immediately at startup instead of producing confusing failures on the first command.","triggerScenarios":"Calling redis.NewClient(nil); passing a *redis.Options variable that was declared but never initialized (still nil); loading options from a builder/config function that returns nil on an unhandled branch; conditionally constructing options where the nil case is not covered.","commonSituations":"Config loaded from environment/flags where no values were set so the builder returns nil; DI containers or factories that return a typed nil (*redis.Options)(nil); tests that stub the options loader to return nil; refactoring that moves option construction behind a helper which forgets to initialize the struct.","solutions":["Ensure the *redis.Options passed to NewClient is always non-nil - construct it inline with &redis.Options{Addr: ...} at minimum.","If options come from a loader/builder, have it always return a valid *redis.Options (defaulting fields) and never nil; add a unit test asserting non-nil.","Guard the call site: if opt == nil { opt = &redis.Options{Addr: \"localhost:6379\"} } before NewClient, or return an error from your own factory.","Run go vet / staticcheck to catch obviously-nil pointers; add a constructor wrapper that fails fast with a clear error instead of a panic."],"exampleFix":"// before\nclient := redis.NewClient(optFromConfig) // optFromConfig is nil\n\n// after\nopt := optFromConfig\nif opt == nil {\n    opt = &redis.Options{Addr: \"localhost:6379\"}\n}\nclient := redis.NewClient(opt)","handlingStrategy":"validation","validationCode":"func newClient(opt *redis.Options) (*redis.Client, error) {\n    if opt == nil {\n        return nil, errors.New(\"redis options must not be nil\")\n    }\n    if opt.Addr == \"\" {\n        opt.Addr = \"localhost:6379\"\n    }\n    return redis.NewClient(opt), nil\n}","typeGuard":"// Guard against a typed-nil or unset pointer before constructing.\nfunc validOptions(opt *redis.Options) bool {\n    return opt != nil\n}","tryCatchPattern":"// Go has no catch; use recover at a goroutine boundary only as a last resort.\ndefer func() {\n    if r := recover(); r != nil {\n        log.Fatalf(\"redis client init failed: %v\", r)\n    }\n}()\nclient := redis.NewClient(opt)","preventionTips":["Always initialize options inline: &redis.Options{Addr: ...}.","Make option loaders return a non-nil *redis.Options with defaults, never nil.","Wrap NewClient in your own factory that validates inputs and returns an error.","Add a unit test asserting your config builder returns non-nil options."],"tags":["client","panic","configuration","constructor","nil-guard"],"analyzedSha":"36d97525cd8076aed67cddf54778e9ea84550929","analyzedAt":"2026-08-06T01:08:27.376Z","schemaVersion":2}