{"record":{"id":"c1833ec4eefcb6d8","repo":"fatedier/frp","slug":"range-number-is-invalid","errorCode":null,"errorMessage":"range number is invalid","messagePattern":"range number is invalid","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/config/types/types.go","lineNumber":165,"sourceCode":"\t\tcase 1:\n\t\t\t// single number\n\t\t\tsingleNum, err := strconv.ParseInt(strings.TrimSpace(numArray[0]), 10, 64)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"range number is invalid, %v\", err)\n\t\t\t}\n\t\t\tout = append(out, PortsRange{Single: int(singleNum)})\n\t\tcase 2:\n\t\t\t// range numbers\n\t\t\tminNum, err := strconv.ParseInt(strings.TrimSpace(numArray[0]), 10, 64)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"range number is invalid, %v\", err)\n\t\t\t}\n\t\t\tmaxNum, err := strconv.ParseInt(strings.TrimSpace(numArray[1]), 10, 64)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"range number is invalid, %v\", err)\n\t\t\t}\n\t\t\tif maxNum < minNum {\n\t\t\t\treturn nil, fmt.Errorf(\"range number is invalid\")\n\t\t\t}\n\t\t\tout = append(out, PortsRange{Start: int(minNum), End: int(maxNum)})\n\t\tdefault:\n\t\t\treturn nil, fmt.Errorf(\"range number is invalid\")\n\t\t}\n\t}\n\treturn out, nil\n}\n","sourceCodeStart":147,"sourceCodeEnd":174,"githubUrl":"https://github.com/fatedier/frp/blob/6c8a8d0a97d03b44e9528d30b30c70cb9d61b405/pkg/config/types/types.go#L147-L174","documentation":"Thrown while unmarshalling a PortsRange when a two-part range has maxNum < minNum, i.e. the range is inverted. Both bounds parsed successfully as integers, but PortsRange semantics require Start <= End, so an inverted range is rejected with this plain (non-wrapped) message from PortsRange.UnmarshalJSON.","triggerScenarios":"PortsRange unmarshal on any token 'X-Y' where Y < X, e.g. \"9000-2000\". Also from auto-generated configs where min/max variables were swapped during templating.","commonSituations":"Hand-written configs with the bounds reversed; generated configs where $MIN and $MAX env vars are interchanged; refactoring that renamed bounds and swapped their order.","solutions":["Swap the bounds so the smaller number comes first: \"2000-9000\".","If the config is generated, fix the template so min is emitted before max.","If a reversed range was intentional (it is not supported), split into explicit individual ports or reorder at the source."],"exampleFix":"# before\nremotePort = \"9000-2000\"\n\n# after\nremotePort = \"2000-9000\"","handlingStrategy":"validation","validationCode":"// Ensure every range token satisfies min <= max.\nfunc rangesOrdered(s string) error {\n\tfor _, tok := range strings.Split(s, \",\") {\n\t\tparts := strings.Split(tok, \"-\")\n\t\tif len(parts) != 2 {\n\t\t\tcontinue\n\t\t}\n\t\tmin, err1 := strconv.ParseInt(strings.TrimSpace(parts[0]), 10, 64)\n\t\tmax, err2 := strconv.ParseInt(strings.TrimSpace(parts[1]), 10, 64)\n\t\tif err1 != nil || err2 != nil {\n\t\t\tcontinue\n\t\t}\n\t\tif max < min {\n\t\t\treturn fmt.Errorf(\"inverted range %q: max %d < min %d\", tok, max, min)\n\t\t}\n\t}\n\treturn nil\n}","typeGuard":"func isOrderedRange(tok string) bool {\n\tparts := strings.Split(tok, \"-\")\n\tif len(parts) != 2 {\n\t\treturn true\n\t}\n\tmin, e1 := strconv.ParseInt(strings.TrimSpace(parts[0]), 10, 64)\n\tmax, e2 := strconv.ParseInt(strings.TrimSpace(parts[1]), 10, 64)\n\treturn e1 == nil && e2 == nil && min <= max\n}","tryCatchPattern":null,"preventionTips":["Always write ranges smallest-to-largest.","In generated configs, emit min(maxvar, minvar) and max(...) to guard swapped variables.","Add a lint rule rejecting inverted ranges."],"tags":["go","frp","config","json","port-range","validation"],"backgroundTag":null,"analyzedSha":"6c8a8d0a97d03b44e9528d30b30c70cb9d61b405","analyzedAt":"2026-08-15T06:53:27.215Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}