{"record":{"id":"f0047e779e16727d","repo":"semaphoreui/semaphore","slug":"invalid-migration-version-patch-part-s","errorCode":null,"errorMessage":"invalid migration version patch part %s","messagePattern":"invalid migration version patch part (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"db/Migration.go","lineNumber":185,"sourceCode":"\tif err != nil {\n\t\terr = fmt.Errorf(\"invalid migration version major part %s\", parts[0])\n\t\treturn\n\t}\n\n\tres.Minor, err = strconv.Atoi(parts[1])\n\tif err != nil {\n\t\terr = fmt.Errorf(\"invalid migration version minor part %s\", parts[1])\n\t\treturn\n\t}\n\n\tif len(parts) < 3 {\n\t\tres.Patch = math.MaxInt\n\t\treturn\n\t}\n\n\tres.Patch, err = strconv.Atoi(parts[2])\n\tif err != nil {\n\t\terr = fmt.Errorf(\"invalid migration version patch part %s\", parts[2])\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc (v MigrationVersion) Compare(o MigrationVersion) int {\n\tif v.Major < o.Major {\n\t\treturn -1\n\t} else if v.Major > o.Major {\n\t\treturn 1\n\t}\n\n\tif v.Minor < o.Minor {\n\t\treturn -1\n\t} else if v.Minor > o.Minor {\n\t\treturn 1\n\t}","sourceCodeStart":167,"sourceCodeEnd":203,"githubUrl":"https://github.com/semaphoreui/semaphore/blob/1774ccb71a0a8b82eb74ea24c23ac9ab713de2fa/db/Migration.go#L167-L203","documentation":"ParseVersion parses an optional third segment of a migration version string into the Patch field. If parts[2] exists but strconv.Atoi fails, it returns 'invalid migration version patch part %s'. Versions with only two parts are valid (Patch defaults to MaxInt).","triggerScenarios":"Calling Compare/ParseVersion with a three-segment version whose patch segment is non-numeric, e.g. '1.2.hotfix' or '1.2.'.","commonSituations":"Appending labels like 'hotfix' or 'rc1' to the patch position; trailing dots in copied version strings; auto-generated names where patch is a date fragment with non-numeric chars.","solutions":["Make the patch segment a plain integer, e.g. '1.2.3' instead of '1.2.hotfix'","Drop the third segment if no numeric patch is needed — '1.2' is valid and yields Patch=MaxInt","Trim stray dots/whitespace from version strings before parsing"],"exampleFix":"// before\nversion = \"1.2.rc1\"\n// after\nversion = \"1.2.1\"","handlingStrategy":"validation","validationCode":"parts := strings.Split(version, \".\")\nif len(parts) > 2 {\n    if _, err := strconv.Atoi(parts[2]); err != nil {\n        return fmt.Errorf(\"patch part %q is not numeric\", parts[2])\n    }\n}","typeGuard":null,"tryCatchPattern":"if err := Compare(a, b); err != nil {\n    if strings.Contains(err.Error(), \"patch part\") {\n        return fmt.Errorf(\"use major.minor.patch with numeric patch: %w\", err)\n    }\n    return err\n}","preventionTips":["Use numeric patch segments only","Omit the third segment entirely if no patch exists","Reject version strings with trailing dots"],"tags":["database","migration","parsing"],"backgroundTag":"invalid-argument-format","analyzedSha":"1774ccb71a0a8b82eb74ea24c23ac9ab713de2fa","analyzedAt":"2026-09-07T11:00:33.293Z","contentChangedAt":"2026-09-07T11:00:33.293Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}