fatedier/frp · error

line %d: field "%s": cannot unmarshal %s into %s

Error message

line %d: field "%s": cannot unmarshal %s into %s

What it means

enhanceDecodeError upgrades encoding/json UnmarshalTypeError for JSON/YAML frp configs into a message with the 1-indexed line where the field appears (findFieldLineInContent scans the raw file), the field path, the value Go saw, and the target type. It fires when a field decodes to a value of the wrong Go type, e.g. a string given where an int is required.

Source

Thrown at pkg/config/load.go:240

	if errors.As(err, &decErr) {
		row, col := decErr.Position()
		return fmt.Errorf("toml: line %d, column %d: %s", row, col, decErr.Error())
	}
	var strictErr *toml.StrictMissingError
	if errors.As(err, &strictErr) {
		return strictErr
	}
	return err
}

// enhanceDecodeError tries to add field path and line number information to JSON/YAML decode errors.
func enhanceDecodeError(err error, originalContent []byte, includeLine bool) error {
	var typeErr *json.UnmarshalTypeError
	if errors.As(err, &typeErr) && typeErr.Field != "" {
		if includeLine {
			line := findFieldLineInContent(originalContent, typeErr.Field)
			if line > 0 {
				return fmt.Errorf("line %d: field \"%s\": cannot unmarshal %s into %s", line, typeErr.Field, typeErr.Value, typeErr.Type)
			}
		}
		return fmt.Errorf("field \"%s\": cannot unmarshal %s into %s", typeErr.Field, typeErr.Value, typeErr.Type)
	}
	return err
}

// findFieldLineInContent searches the original config content for a field name
// and returns the 1-indexed line number where it appears, or 0 if not found.
func findFieldLineInContent(content []byte, fieldPath string) int {
	if fieldPath == "" {
		return 0
	}

	// Use the last component of the field path (e.g. "proxies" from "proxies" or
	// "protocol" from "transport.protocol").
	parts := strings.Split(fieldPath, ".")
	searchKey := parts[len(parts)-1]

View on GitHub (pinned to 6c8a8d0a97)

Solutions

  1. Go to the reported line and change the value to the type shown in the message (e.g. remove quotes around integers)
  2. Check the frp schema/docs for the field's expected type
  3. Re-run frpc verify until decoding succeeds

Example fix

# before (line 8: field "heartbeatInterval": cannot unmarshal string into int64)
transport.heartbeatInterval = "30"

# after
transport.heartbeatInterval = 30
Defensive patterns

Strategy: validation

Validate before calling

// schema-check JSON content before load
var generic map[string]any
if err := json.Unmarshal(raw, &generic); err == nil { _ = checkTypesAgainstSchema(generic, schema) } // custom walker comparing kinds

Try / catch

if err := config.LoadConfigureFromFile(path, &cfg, strict); err != nil { var te *json.UnmarshalTypeError; if errors.As(err, &te) { /* te.Field, te.Value, te.Type available for targeted message */ } }

Prevention

When it happens

Trigger: A YAML/TOML/JSON client or server config where e.g. transport.heartbeatInterval is set to "30" (string) instead of 30, or a list is given for a scalar field; includeLine is true for file-based loads so the line is resolved from the original bytes.

Common situations: Quoting numbers in YAML; pasting values from web UIs that stringify everything; struct changes across frp versions altering a field's expected type.

Related errors


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