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
- Go to the reported line and change the value to the type shown in the message (e.g. remove quotes around integers)
- Check the frp schema/docs for the field's expected type
- 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
- Never quote numeric config values
- Use frp's JSON schema in the editor
- Verify generated configs with frpc verify
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
- field "%s": cannot unmarshal %s into %s
- failed to parse JSON: %w
- plugin type is empty
- visitor plugin type is empty
- toml: line %d, column %d: %s
AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15).
Data as JSON: /api/errors/a00d006d1bf356d8.
Report an issue: GitHub.