fatedier/frp · error

range number is invalid

Error message

range number is invalid

What it means

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.

Source

Thrown at pkg/config/types/types.go:165

		case 1:
			// single number
			singleNum, err := strconv.ParseInt(strings.TrimSpace(numArray[0]), 10, 64)
			if err != nil {
				return nil, fmt.Errorf("range number is invalid, %v", err)
			}
			out = append(out, PortsRange{Single: int(singleNum)})
		case 2:
			// range numbers
			minNum, err := strconv.ParseInt(strings.TrimSpace(numArray[0]), 10, 64)
			if err != nil {
				return nil, fmt.Errorf("range number is invalid, %v", err)
			}
			maxNum, err := strconv.ParseInt(strings.TrimSpace(numArray[1]), 10, 64)
			if err != nil {
				return nil, fmt.Errorf("range number is invalid, %v", err)
			}
			if maxNum < minNum {
				return nil, fmt.Errorf("range number is invalid")
			}
			out = append(out, PortsRange{Start: int(minNum), End: int(maxNum)})
		default:
			return nil, fmt.Errorf("range number is invalid")
		}
	}
	return out, nil
}

View on GitHub (pinned to 6c8a8d0a97)

Solutions

  1. Swap the bounds so the smaller number comes first: "2000-9000".
  2. If the config is generated, fix the template so min is emitted before max.
  3. If a reversed range was intentional (it is not supported), split into explicit individual ports or reorder at the source.

Example fix

# before
remotePort = "9000-2000"

# after
remotePort = "2000-9000"
Defensive patterns

Strategy: validation

Validate before calling

// Ensure every range token satisfies min <= max.
func rangesOrdered(s string) error {
	for _, tok := range strings.Split(s, ",") {
		parts := strings.Split(tok, "-")
		if len(parts) != 2 {
			continue
		}
		min, err1 := strconv.ParseInt(strings.TrimSpace(parts[0]), 10, 64)
		max, err2 := strconv.ParseInt(strings.TrimSpace(parts[1]), 10, 64)
		if err1 != nil || err2 != nil {
			continue
		}
		if max < min {
			return fmt.Errorf("inverted range %q: max %d < min %d", tok, max, min)
		}
	}
	return nil
}

Type guard

func isOrderedRange(tok string) bool {
	parts := strings.Split(tok, "-")
	if len(parts) != 2 {
		return true
	}
	min, e1 := strconv.ParseInt(strings.TrimSpace(parts[0]), 10, 64)
	max, e2 := strconv.ParseInt(strings.TrimSpace(parts[1]), 10, 64)
	return e1 == nil && e2 == nil && min <= max
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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