fatedier/frp · error

unmarshal VisitorConfig error: %v

Error message

unmarshal VisitorConfig error: %v

What it means

DecodeVisitorConfigurerJSON fails at decodeJSONWithOptions when the visitor object's fields cannot be unmarshalled into the typed VisitorConfigurer selected by "type". The underlying json error is wrapped as 'unmarshal VisitorConfig error'. The visitor type was recognized, but field names or value types inside the visitor object don't match its struct.

Source

Thrown at pkg/config/v1/decode.go:87

	return configurer, nil
}

func DecodeVisitorConfigurerJSON(b []byte, options DecodeOptions) (VisitorConfigurer, error) {
	if isJSONNull(b) {
		return nil, errors.New("type is required")
	}

	var env typedEnvelope
	if err := jsonx.Unmarshal(b, &env); err != nil {
		return nil, err
	}

	configurer := NewVisitorConfigurerByType(VisitorType(env.Type))
	if configurer == nil {
		return nil, fmt.Errorf("unknown visitor type: %s", env.Type)
	}
	if err := decodeJSONWithOptions(b, configurer, options); err != nil {
		return nil, fmt.Errorf("unmarshal VisitorConfig error: %v", err)
	}

	if len(env.Plugin) > 0 && !isJSONNull(env.Plugin) {
		plugin, err := DecodeVisitorPluginOptionsJSON(env.Plugin, options)
		if err != nil {
			return nil, fmt.Errorf("unmarshal visitor plugin error: %v", err)
		}
		configurer.GetBaseConfig().Plugin = plugin
	}
	return configurer, nil
}

func DecodeClientPluginOptionsJSON(b []byte, options DecodeOptions) (TypedClientPluginOptions, error) {
	if isJSONNull(b) {
		return TypedClientPluginOptions{}, nil
	}

	var env typedEnvelope

View on GitHub (pinned to 6c8a8d0a97)

Solutions

  1. Read the wrapped error — it identifies the exact field; correct its type (numbers as numbers, strings as strings).
  2. Align field names with the visitor schema for the declared type (name, secretKey, serverName, bindAddr, bindPort).
  3. Remove unrecognized fields or relax strict decoding in DecodeOptions.

Example fix

// before
{ "type": "stcp", "bindPort": "6000" }

// after
{ "type": "stcp", "bindPort": 6000 }
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-check visitor entries for obviously wrong field types.
func lintVisitorJSON(b []byte) error {
	var probe struct {
		Type     string `json:"type"`
		BindPort any    `json:"bindPort"`
	}
	if err := json.Unmarshal(b, &probe); err != nil {
		return err
	}
	if bp, ok := probe.BindPort.(string); ok {
		return fmt.Errorf("bindPort must be a number, got string %q", bp)
	}
	return nil
}

Try / catch

if _, err := v1.DecodeClientConfigJSON(b, opts); err != nil {
	if strings.Contains(err.Error(), "unmarshal VisitorConfig error") {
		// wrapped json error names the field; fix that visitors[] entry
	}
	return err
}

Prevention

When it happens

Trigger: A visitor entry with wrong-typed fields (e.g. "bindPort": "6000" as a string, or "serverName" given as a number), or unknown fields under strict DecodeOptions. Reached via DecodeClientConfigJSON's visitors loop or a direct DecodeVisitorConfigurerJSON call.

Common situations: Config migration tools emitting strings for numeric fields; stale field names from older frp versions (secretKey casing); strict mode rejecting extra keys.

Related errors


AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15). Data as JSON: /api/errors/67561d853839dfcb. Report an issue: GitHub.