{"record":{"id":"16ddb5a031e59146","repo":"fatedier/frp","slug":"unknown-visitor-type-s","errorCode":null,"errorMessage":"unknown visitor type: %s","messagePattern":"unknown visitor type: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/config/v1/decode.go","lineNumber":84,"sourceCode":"\t\t}\n\t\tconfigurer.GetBaseConfig().Plugin = plugin\n\t}\n\treturn configurer, nil\n}\n\nfunc DecodeVisitorConfigurerJSON(b []byte, options DecodeOptions) (VisitorConfigurer, error) {\n\tif isJSONNull(b) {\n\t\treturn nil, errors.New(\"type is required\")\n\t}\n\n\tvar env typedEnvelope\n\tif err := jsonx.Unmarshal(b, &env); err != nil {\n\t\treturn nil, err\n\t}\n\n\tconfigurer := NewVisitorConfigurerByType(VisitorType(env.Type))\n\tif configurer == nil {\n\t\treturn nil, fmt.Errorf(\"unknown visitor type: %s\", env.Type)\n\t}\n\tif err := decodeJSONWithOptions(b, configurer, options); err != nil {\n\t\treturn nil, fmt.Errorf(\"unmarshal VisitorConfig error: %v\", err)\n\t}\n\n\tif len(env.Plugin) > 0 && !isJSONNull(env.Plugin) {\n\t\tplugin, err := DecodeVisitorPluginOptionsJSON(env.Plugin, options)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"unmarshal visitor plugin error: %v\", err)\n\t\t}\n\t\tconfigurer.GetBaseConfig().Plugin = plugin\n\t}\n\treturn configurer, nil\n}\n\nfunc DecodeClientPluginOptionsJSON(b []byte, options DecodeOptions) (TypedClientPluginOptions, error) {\n\tif isJSONNull(b) {\n\t\treturn TypedClientPluginOptions{}, nil","sourceCodeStart":66,"sourceCodeEnd":102,"githubUrl":"https://github.com/fatedier/frp/blob/6c8a8d0a97d03b44e9528d30b30c70cb9d61b405/pkg/config/v1/decode.go#L66-L102","documentation":"DecodeVisitorConfigurerJSON mirrors the proxy path: it reads the envelope \"type\" and calls NewVisitorConfigurerByType. If the type is not a registered visitor type (stcp, sudp, xtcp are the visitors' backing types), the factory returns nil and this error names the unknown visitor type. It fires before any field-level decoding.","triggerScenarios":"Calling DecodeVisitorConfigurerJSON (or DecodeClientConfigJSON over a visitors[] entry) with type set to a proxy-only type like \"tcp\", a misspelled value like \"STCP\", or a plugin name. Only visitor-capable types resolve to a VisitorConfigurer.","commonSituations":"Copy-pasting a proxy block into the visitors section without changing type; assuming every proxy type has a visitor counterpart; case mismatches after config migration.","solutions":["Use a visitor-supported type: \"stcp\", \"sudp\", or \"xtcp\" in the visitors section.","Verify the type string is lowercase and exactly spelled.","Move plain tcp/udp entries to the proxies section — they cannot be visitors."],"exampleFix":"// before\n[[visitors]]\ntype = \"tcp\"\n\n// after\n[[visitors]]\ntype = \"stcp\"","handlingStrategy":"validation","validationCode":"// Only these types have visitor configurers.\nfunc validVisitorType(t string) error {\n\tswitch t {\n\tcase \"stcp\", \"sudp\", \"xtcp\":\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"unknown visitor type %q; want stcp, sudp, or xtcp\", t)\n}","typeGuard":"func isKnownVisitorType(t string) bool {\n\tswitch t {\n\tcase \"stcp\", \"sudp\", \"xtcp\":\n\t\treturn true\n\t}\n\treturn false\n}","tryCatchPattern":"if _, err := v1.DecodeClientConfigJSON(b, opts); err != nil {\n\tif strings.Contains(err.Error(), \"unknown visitor type\") {\n\t\t// fix the visitors[] entry type; tcp/udp belong in proxies[]\n\t}\n\treturn err\n}","preventionTips":["Remember only stcp/sudp/xtcp support visitors.","Don't clone proxy blocks into the visitors section unchanged.","Keep visitor type strings lowercase."],"tags":["go","frp","config","json","visitor","decode"],"backgroundTag":null,"analyzedSha":"6c8a8d0a97d03b44e9528d30b30c70cb9d61b405","analyzedAt":"2026-08-15T06:53:27.215Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}