{"record":{"id":"2e79cd105cdbc5ad","repo":"semaphoreui/semaphore","slug":"invalid-migration-version-major-part-s","errorCode":null,"errorMessage":"invalid migration version major part %s","messagePattern":"invalid migration version major part (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"db/Migration.go","lineNumber":168,"sourceCode":"\ntype MigrationVersion struct {\n\tMajor int\n\tMinor int\n\tPatch int\n}\n\nfunc (m Migration) ParseVersion() (res MigrationVersion, err error) {\n\n\tparts := strings.Split(m.Version, \".\")\n\n\tif len(parts) < 2 {\n\t\terr = fmt.Errorf(\"invalid migration version format %s\", m.Version)\n\t\treturn\n\t}\n\n\tres.Major, err = strconv.Atoi(parts[0])\n\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","sourceCodeStart":150,"sourceCodeEnd":186,"githubUrl":"https://github.com/semaphoreui/semaphore/blob/1774ccb71a0a8b82eb74ea24c23ac9ab713de2fa/db/Migration.go#L150-L186","documentation":"ParseVersion parses a migration version string in 'major[.minor][.patch]' form. When the major component cannot be converted to an integer with strconv.Atoi, it returns 'invalid migration version major part %s'. This is a fail-fast guard so migration ordering (via Compare) never operates on garbage versions.","triggerScenarios":"Calling Compare (which calls ParseVersion) with a migration whose Version's first dot-separated segment is non-numeric, e.g. 'v1.0.0', 'alpha.2', or '.3'.","commonSituations":"Hand-written migration filenames/versions that include a 'v' prefix, letters, or leading separators; renaming migration files without keeping numeric prefixes; scripts that generate versions from timestamps with non-numeric separators.","solutions":["Rename the migration version so the first segment is a plain integer, e.g. '1' or '1.0.0' instead of 'v1.0.0'","Strip non-numeric characters (like a leading 'v') before passing the version string into Compare/ParseVersion","Add a pre-commit/CI check that validates every migration version parses before it is committed"],"exampleFix":"// before\nCompare(\"v1.2.0\", \"1.3.0\")\n// after\nCompare(\"1.2.0\", \"1.3.0\")","handlingStrategy":"validation","validationCode":"var versionRe = regexp.MustCompile(`^\\d+(\\.\\d+)?(\\.\\d+)?$`)\nfunc validMigrationVersion(v string) bool { return versionRe.MatchString(strings.TrimPrefix(v, \"v\")) }","typeGuard":null,"tryCatchPattern":"if err := Compare(a, b); err != nil {\n    if strings.Contains(err.Error(), \"invalid migration version\") {\n        log.Fatalf(\"bad migration version: %v\", err)\n    }\n    return err\n}","preventionTips":["Use plain numeric 'major[.minor][.patch]' version strings in migration filenames","Strip 'v' prefixes and other decorations before parsing","Add a CI check that parses every migration version"],"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"}