{"record":{"id":"85b141fedcdb44d6","repo":"fatedier/frp","slug":"visitor-name-cannot-be-empty","errorCode":null,"errorMessage":"visitor name cannot be empty","messagePattern":"visitor name cannot be empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/config/source/store.go","lineNumber":117,"sourceCode":"\t\t\treturn fmt.Errorf(\"failed to decode proxy at index %d: %w\", i, err)\n\t\t}\n\t\tname := proxyCfg.GetBaseConfig().Name\n\t\tif name == \"\" {\n\t\t\treturn fmt.Errorf(\"proxy name cannot be empty\")\n\t\t}\n\t\ts.proxies[name] = proxyCfg\n\t}\n\n\tfor i, visitorData := range stored.Visitors {\n\t\tvisitorCfg, err := v1.DecodeVisitorConfigurerJSON(visitorData, v1.DecodeOptions{\n\t\t\tDisallowUnknownFields: false,\n\t\t})\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to decode visitor at index %d: %w\", i, err)\n\t\t}\n\t\tname := visitorCfg.GetBaseConfig().Name\n\t\tif name == \"\" {\n\t\t\treturn fmt.Errorf(\"visitor name cannot be empty\")\n\t\t}\n\t\ts.visitors[name] = visitorCfg\n\t}\n\n\treturn nil\n}\n\nfunc (s *StoreSource) saveToFileUnlocked() error {\n\tstored := storeData{\n\t\tProxies:  make([]v1.TypedProxyConfig, 0, len(s.proxies)),\n\t\tVisitors: make([]v1.TypedVisitorConfig, 0, len(s.visitors)),\n\t}\n\n\tfor _, p := range s.proxies {\n\t\tstored.Proxies = append(stored.Proxies, v1.TypedProxyConfig{ProxyConfigurer: p})\n\t}\n\tfor _, v := range s.visitors {\n\t\tstored.Visitors = append(stored.Visitors, v1.TypedVisitorConfig{VisitorConfigurer: v})","sourceCodeStart":99,"sourceCodeEnd":135,"githubUrl":"https://github.com/fatedier/frp/blob/6c8a8d0a97d03b44e9528d30b30c70cb9d61b405/pkg/config/source/store.go#L99-L135","documentation":"A visitor entry in the store file decoded successfully but its base config Name is empty. Visitors are stored in a map keyed by name, so a nameless visitor entry makes the whole file unloadable; loading aborts at the first such entry.","triggerScenarios":"A visitors array element in the store file that omits \"name\" or sets it to \"\". Typically from hand-editing or a code-generated store where the name field was skipped for one entry.","commonSituations":"Template/script generating visitor entries without names; migration tools that drop empty-but-present fields and accidentally drop name; copy-paste of an example visitor config that used a placeholder.","solutions":["Scan the visitors array in the store file for entries missing \"name\"","Give every visitor a unique non-empty name","Remove incomplete entries if unintended","Retry loading"],"exampleFix":"// before\n{ \"visitors\": [ { \"type\": \"stcp\", \"serverName\": \"ssh\" } ] }\n\n// after\n{ \"visitors\": [ { \"type\": \"stcp\", \"name\": \"ssh-v\", \"serverName\": \"ssh\" } ] }","handlingStrategy":"validation","validationCode":"func validateVisitorNames(path string) error {\n\tdata, _ := os.ReadFile(path)\n\tvar raw struct {\n\t\tVisitors []struct{ Name string `json:\"name\"` } `json:\"visitors\"`\n\t}\n\tif json.Unmarshal(data, &raw) != nil {\n\t\treturn nil\n\t}\n\tfor i, v := range raw.Visitors {\n\t\tif strings.TrimSpace(v.Name) == \"\" {\n\t\t\treturn fmt.Errorf(\"visitors[%d] has empty name\", i)\n\t\t}\n\t}\n\treturn nil\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Make visitor name mandatory in provisioning templates","Derive visitor names deterministically (e.g. serverName + \"-v\") and assert non-empty in code","Round-trip test generated stores: write then reload with NewStoreSource in CI"],"tags":["json","config","validation","required-field","visitor","go"],"backgroundTag":null,"analyzedSha":"6c8a8d0a97d03b44e9528d30b30c70cb9d61b405","analyzedAt":"2026-08-15T06:53:27.215Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}