semaphoreui/semaphore · error

invalid migration version patch part

Error message

invalid migration version patch part %s

What it means

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).

Solutions

  1. Make the patch segment a plain integer, e.g. '1.2.3' instead of '1.2.hotfix'
  2. Drop the third segment if no numeric patch is needed — '1.2' is valid and yields Patch=MaxInt
  3. Trim stray dots/whitespace from version strings before parsing

Example fix

// before
version = "1.2.rc1"
// after
version = "1.2.1"
Defensive patterns

Strategy: validation

Validate before calling

parts := strings.Split(version, ".")
if len(parts) > 2 {
    if _, err := strconv.Atoi(parts[2]); err != nil {
        return fmt.Errorf("patch part %q is not numeric", parts[2])
    }
}

Try / catch

if err := Compare(a, b); err != nil {
    if strings.Contains(err.Error(), "patch part") {
        return fmt.Errorf("use major.minor.patch with numeric patch: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: Calling Compare/ParseVersion with a three-segment version whose patch segment is non-numeric, e.g. '1.2.hotfix' or '1.2.'.

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

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/f0047e779e16727d. Report an issue: GitHub.

Appendix: source

Thrown at db/Migration.go:185

	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
	}

	return
}

func (v MigrationVersion) Compare(o MigrationVersion) int {
	if v.Major < o.Major {
		return -1
	} else if v.Major > o.Major {
		return 1
	}

	if v.Minor < o.Minor {
		return -1
	} else if v.Minor > o.Minor {
		return 1
	}

View on GitHub (pinned to 1774ccb71a)