{"record":{"id":"83b088043a4015e8","repo":"XTLS/Xray-core","slug":"invalid-version-component-s-in-s","errorCode":null,"errorMessage":"invalid version component %s in %s","messagePattern":"invalid version component (.+?) in (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/version/version.go","lineNumber":57,"sourceCode":"func compareVersions(v1, v2 string) (int, error) {\n\t// Split version strings into components\n\tv1Parts := strings.Split(v1, \".\")\n\tv2Parts := strings.Split(v2, \".\")\n\n\t// Pad shorter versions with zeros\n\tfor len(v1Parts) < len(v2Parts) {\n\t\tv1Parts = append(v1Parts, \"0\")\n\t}\n\tfor len(v2Parts) < len(v1Parts) {\n\t\tv2Parts = append(v2Parts, \"0\")\n\t}\n\n\t// Compare each part\n\tfor i := 0; i < len(v1Parts); i++ {\n\t\t// Convert parts to integers\n\t\tn1, err := strconv.Atoi(v1Parts[i])\n\t\tif err != nil {\n\t\t\treturn 0, errors.New(\"invalid version component \", v1Parts[i], \" in \", v1)\n\t\t}\n\t\tn2, err := strconv.Atoi(v2Parts[i])\n\t\tif err != nil {\n\t\t\treturn 0, errors.New(\"invalid version component \", v2Parts[i], \" in \", v2)\n\t\t}\n\n\t\tif n1 < n2 {\n\t\t\treturn -1, nil // v1 < v2\n\t\t}\n\t\tif n1 > n2 {\n\t\t\treturn 1, nil // v1 > v2\n\t\t}\n\t}\n\treturn 0, nil // v1 == v2\n}\n\nfunc init() {\n\tcommon.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/XTLS/Xray-core/blob/7d214f8b094f75322fa3990f8aadad1c912f24f5/app/version/version.go#L39-L75","documentation":"Returned by compareVersions in app/version when the first version string (config.MinVersion/MaxVersion) contains a component that is not a plain decimal integer — strconv.Atoi fails on that dot-separated part. Both endpoints of the comparison can produce it; this one wraps the MinVersion-side component (v1). Versions must be numeric dotted triples/quads like 25.8.15; the parser does not accept suffixes.","triggerScenarios":"A minVersion/maxVersion such as \"v25.8.15\" (leading 'v'), \"25.8.15-beta\" (pre-release suffix), \"1.2.x\", or an empty component \"25..8\". Any non-digit component in v1 raises this at startup during version.New.","commonSituations":"Hand-editing configs and pasting versions from release tags ('v' prefix from GitHub tags); tooling that appends build metadata; assuming semver pre-release strings are supported when only numeric parts are.","solutions":["Strip any leading 'v'/'V' and pre-release/build suffixes so every dot-separated component is an integer (e.g. \"v25.8.15-beta\" -> \"25.8.15\").","Validate the version fields with a numeric-component check before writing configs (see validationCode).","If you need pre-release gating, enforce it outside the config version pins; Xray compares numeric components only."],"exampleFix":"// before (config)\n\"minVersion\": \"v25.8.15\"\n\n// after\n\"minVersion\": \"25.8.15\"","handlingStrategy":"validation","validationCode":"func validVersionPin(v string) bool {\n    if v == \"\" { return true } // optional\n    for _, p := range strings.Split(v, \".\") {\n        if _, err := strconv.Atoi(p); err != nil { return false }\n    }\n    return true\n}\n// reject config if !validVersionPin(cfg.MinVersion) || !validVersionPin(cfg.MaxVersion)","typeGuard":"func isNumericVersion(v string) bool {\n    for _, p := range strings.Split(v, \".\") {\n        if _, err := strconv.Atoi(p); err != nil { return false }\n    }\n    return true\n}","tryCatchPattern":null,"preventionTips":["Strip 'v' prefixes and pre-release suffixes when generating configs","Lint config version fields in CI"],"tags":["version","config-validation","parsing","xray"],"backgroundTag":null,"analyzedSha":"7d214f8b094f75322fa3990f8aadad1c912f24f5","analyzedAt":"2026-08-15T14:26:24.325Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}