{"record":{"id":"abe021e96ee00528","repo":"caddyserver/caddy","slug":"moduleinfo-new-must-return-a-non-nil-module-instan","errorCode":null,"errorMessage":"ModuleInfo.New must return a non-nil module instance","messagePattern":"ModuleInfo\\.New must return a non-nil module instance","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"modules.go","lineNumber":151,"sourceCode":"// init phase of runtime. Typically, the module package\n// will do this as a side-effect of being imported.\n// This function panics if the module's info is\n// incomplete or invalid, or if the module is already\n// registered.\nfunc RegisterModule(instance Module) {\n\tmod := instance.CaddyModule()\n\n\tif mod.ID == \"\" {\n\t\tpanic(\"module ID missing\")\n\t}\n\tif mod.ID == \"caddy\" || mod.ID == \"admin\" {\n\t\tpanic(fmt.Sprintf(\"module ID '%s' is reserved\", mod.ID))\n\t}\n\tif mod.New == nil {\n\t\tpanic(\"missing ModuleInfo.New\")\n\t}\n\tif val := mod.New(); val == nil {\n\t\tpanic(\"ModuleInfo.New must return a non-nil module instance\")\n\t}\n\n\tmodulesMu.Lock()\n\tdefer modulesMu.Unlock()\n\n\tif _, ok := modules[string(mod.ID)]; ok {\n\t\tpanic(fmt.Sprintf(\"module already registered: %s\", mod.ID))\n\t}\n\tmodules[string(mod.ID)] = mod\n}\n\n// GetModule returns module information from its ID (full name).\nfunc GetModule(name string) (ModuleInfo, error) {\n\tmodulesMu.RLock()\n\tdefer modulesMu.RUnlock()\n\tm, ok := modules[name]\n\tif !ok {\n\t\treturn ModuleInfo{}, fmt.Errorf(\"module not registered: %s\", name)","sourceCodeStart":133,"sourceCodeEnd":169,"githubUrl":"https://github.com/caddyserver/caddy/blob/50e54ee279aa1e504fe218ca49ab6ae16c100410/modules.go#L133-L169","documentation":"RegisterModule smoke-tests the factory by calling New() once; if it returns nil, registration panics. A New that returns a nil interface (often a typed nil pointer or an early return) would break every later instantiation path, so Caddy rejects it up front.","triggerScenarios":"New returning nil explicitly, returning a nil *T stored in the caddy.Module interface (typed nil is non-nil interface but a `return nil` is caught), or logic like `if disabled { return nil }`.","commonSituations":"Conditional factories; refactors that return a nil pointer from a helper; returning a nil interface from a switch over config (though config isn't available at init — the panic still fires if the closure unconditionally returns nil).","solutions":["Make New unconditionally return a new instance: func() caddy.Module { return new(T) }","Do not put environment/config-dependent logic in New; move it to Provision()","If using generics or wrappers, verify the returned value is non-nil before returning it"],"exampleFix":"// before\nNew: func() caddy.Module { var h *MyHandler; return h },\n// after\nNew: func() caddy.Module { return new(MyHandler) },","handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":["New must unconditionally return a fresh non-nil instance","Move conditional logic to Provision()","Avoid returning typed nil pointers from New"],"tags":["caddy","go","module-system","registration","nil-check","panic"],"backgroundTag":null,"analyzedSha":"50e54ee279aa1e504fe218ca49ab6ae16c100410","analyzedAt":"2026-08-15T09:20:21.641Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}