{"record":{"id":"92963c22e721c328","repo":"fatedier/frp","slug":"client-registry-unavailable-92963c","errorCode":null,"errorMessage":"client registry unavailable","messagePattern":"client registry unavailable","errorType":"http","errorClass":null,"httpStatus":500,"severity":"error","filePath":"server/http/controller_v2.go","lineNumber":114,"sourceCode":"\t\treturn nil, err\n\t}\n\n\tcleared, total := mem.StatsCollector.PruneOfflineProxies()\n\treturn model.V2SystemPruneResp{\n\t\tType:    pruneType,\n\t\tCleared: cleared,\n\t\tTotal:   total,\n\t}, nil\n}\n\n// /api/v2/users\nfunc (c *Controller) APIV2UserList(ctx *httppkg.Context) (any, error) {\n\tpage, pageSize, err := parseV2PageParams(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif c.clientRegistry == nil {\n\t\treturn nil, fmt.Errorf(\"client registry unavailable\")\n\t}\n\n\tuserStats := make(map[string]*model.V2UserResp)\n\tfor _, info := range c.clientRegistry.List() {\n\t\titem := getOrCreateV2User(userStats, info.User)\n\t\titem.ClientCount++\n\t}\n\tfor _, proxyInfo := range c.listV2ProxyStats(\"\") {\n\t\titem := getOrCreateV2User(userStats, proxyInfo.User)\n\t\titem.ProxyCount++\n\t}\n\n\tq := strings.ToLower(ctx.Query(\"q\"))\n\titems := make([]model.V2UserResp, 0, len(userStats))\n\tfor _, item := range userStats {\n\t\tif q != \"\" && !strings.Contains(strings.ToLower(item.User), q) {\n\t\t\tcontinue\n\t\t}","sourceCodeStart":96,"sourceCodeEnd":132,"githubUrl":"https://github.com/fatedier/frp/blob/6c8a8d0a97d03b44e9528d30b30c70cb9d61b405/server/http/controller_v2.go#L96-L132","documentation":"The v2 API endpoint /api/v2/users aggregates clients by user via clientRegistry.List(); if the controller was built without a ClientRegistry the nil check fails and the endpoint errors out before pagination results are built.","triggerScenarios":"A GET /api/v2/users request reaches a Controller whose clientRegistry field is nil — custom embedding, tests, or a broken bootstrap — after parseV2PageParams succeeds; the nil guard fires before any aggregation.","commonSituations":"Custom builds or embeddings of frps that register v2 routes but skip registry wiring; startup refactor dropping the registry argument; test controllers with partial fields.","solutions":["Wire the ClientRegistry into the Controller used for v2 routes.","If a registry-less deployment is intentional, disable the v2 API routes.","Add constructor functions (rather than struct literals) so controllers cannot be built half-initialized."],"exampleFix":"// before\nc := &Controller{} // v2 routes registered, registry nil\n\n// after\nc := NewController(clientRegistry, proxyManager)","handlingStrategy":"validation","validationCode":"if controller.ClientRegistry() == nil {\n    return errors.New(\"v2 user API unavailable without registry\")\n}","typeGuard":null,"tryCatchPattern":"// map to 503 and alert on occurrence — it signals a wiring regression, not user error","preventionTips":["Register v2 routes only when a registry exists.","Keep a single bootstrap path for controller construction."],"tags":["go","frp","http-api","v2","dependency-injection"],"backgroundTag":null,"analyzedSha":"6c8a8d0a97d03b44e9528d30b30c70cb9d61b405","analyzedAt":"2026-08-15T06:53:27.215Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}