semaphoreui/semaphore · error

invalid migration version major part

Error message

invalid migration version major part %s

What it means

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.

Solutions

  1. Rename the migration version so the first segment is a plain integer, e.g. '1' or '1.0.0' instead of 'v1.0.0'
  2. Strip non-numeric characters (like a leading 'v') before passing the version string into Compare/ParseVersion
  3. Add a pre-commit/CI check that validates every migration version parses before it is committed

Example fix

// before
Compare("v1.2.0", "1.3.0")
// after
Compare("1.2.0", "1.3.0")
Defensive patterns

Strategy: validation

Validate before calling

var versionRe = regexp.MustCompile(`^\d+(\.\d+)?(\.\d+)?$`)
func validMigrationVersion(v string) bool { return versionRe.MatchString(strings.TrimPrefix(v, "v")) }

Try / catch

if err := Compare(a, b); err != nil {
    if strings.Contains(err.Error(), "invalid migration version") {
        log.Fatalf("bad migration version: %v", err)
    }
    return err
}

Prevention

When it happens

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

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

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of semaphoreui/semaphore@1774ccb71a (2026-09-07). Data as JSON: /api/errors/2e79cd105cdbc5ad. Report an issue: GitHub.

Appendix: source

Thrown at db/Migration.go:168

type MigrationVersion struct {
	Major int
	Minor int
	Patch int
}

func (m Migration) ParseVersion() (res MigrationVersion, err error) {

	parts := strings.Split(m.Version, ".")

	if len(parts) < 2 {
		err = fmt.Errorf("invalid migration version format %s", m.Version)
		return
	}

	res.Major, err = strconv.Atoi(parts[0])
	if err != nil {
		err = fmt.Errorf("invalid migration version major part %s", parts[0])
		return
	}

	res.Minor, err = strconv.Atoi(parts[1])
	if err != nil {
		err = fmt.Errorf("invalid migration version minor part %s", parts[1])
		return
	}

	if len(parts) < 3 {
		res.Patch = math.MaxInt
		return
	}

	res.Patch, err = strconv.Atoi(parts[2])
	if err != nil {
		err = fmt.Errorf("invalid migration version patch part %s", parts[2])
		return

View on GitHub (pinned to 1774ccb71a)