golang-migrate/migrate · error

malformed migration filename: %s

Error message

malformed migration filename: %s

What it means

nextSeqVersion derives the next sequence number by looking at the last existing migration filename in the directory and parsing the digits before the first underscore. If that filename has no underscore (or starts with one), the CLI cannot extract a sequence prefix and refuses to continue rather than guessing a version. This guards against mixing non-sequential files into a seq-numbered migrations directory.

Source

Thrown at internal/cli/commands.go:36

	errInvalidSequenceWidth     = errors.New("digits must be positive")
	errIncompatibleSeqAndFormat = errors.New("the seq and format options are mutually exclusive")
	errInvalidTimeFormat        = errors.New("time format may not be empty")
)

func nextSeqVersion(matches []string, seqDigits int) (string, error) {
	if seqDigits <= 0 {
		return "", errInvalidSequenceWidth
	}

	nextSeq := uint64(1)

	if len(matches) > 0 {
		filename := matches[len(matches)-1]
		matchSeqStr := filepath.Base(filename)
		idx := strings.Index(matchSeqStr, "_")

		if idx < 1 { // Using 1 instead of 0 since there should be at least 1 digit
			return "", fmt.Errorf("malformed migration filename: %s", filename)
		}

		var err error
		matchSeqStr = matchSeqStr[0:idx]
		nextSeq, err = strconv.ParseUint(matchSeqStr, 10, 64)

		if err != nil {
			return "", err
		}

		nextSeq++
	}

	version := fmt.Sprintf("%0[2]*[1]d", nextSeq, seqDigits)

	if len(version) > seqDigits {
		return "", fmt.Errorf("next sequence number %s too large, at most %d digits are allowed", version, seqDigits)
	}

View on GitHub (pinned to 01a9643f14)

Solutions

  1. Rename the offending file(s) in the migrations directory to the standard `<seq>_<name>.<direction><ext>` format, e.g. `3_add_users.up.sql`.
  2. Move non-conforming files (notes, backups, timestamp-named migrations) out of the seq migrations directory.
  3. List files with `ls` / `filepath.Glob(dir/*<ext>)` and verify every file starts with digits followed by an underscore.
  4. Recreate the migration with `migrate create -seq` after cleaning the directory so the sequence can be computed.

Example fix

// before
migrations/foo.up.sql
// after
migrations/000001_foo.up.sql
Defensive patterns

Strategy: validation

Validate before calling

files, _ := filepath.Glob(filepath.Join(dir, "*"+ext))
for _, f := range files {
    base := filepath.Base(f)
    idx := strings.Index(base, "_")
    if idx < 1 {
        return fmt.Errorf("%s must be named <digits>_<name>.<up|down>%s", f, ext)
    }
    if _, err := strconv.ParseUint(base[:idx], 10, 64); err != nil {
        return err
    }
}

Type guard

func hasSeqPrefix(name string) bool {
    idx := strings.Index(name, "_")
    if idx < 1 {
        return false
    }
    _, err := strconv.ParseUint(name[:idx], 10, 64)
    return err == nil
}

Try / catch

if err := createCmd(dir, time.Now(), format, name, ext, seq, seqDigits, print); err != nil {
    if strings.Contains(err.Error(), "malformed migration filename") {
        // prompt to fix/renames the offending file named in the error
    }
    return err
}

Prevention

When it happens

Trigger: Running `migrate create -seq ...` in a directory where the highest-matching migration file (by glob `<dir>_*<ext>`, sorted lexically) has a name without `<digits>_`, e.g. `foo.up.sql` or `_init.up.sql`.

Common situations: A hand-named migration file was dropped into the migrations folder; a file created with the default timestamp format lives next to seq files; an editor/backup file like `~` or `.bak` variants match the glob; someone renamed a migration and removed the numeric prefix.

Understand the failure class

Related errors


AI-assisted analysis of golang-migrate/migrate@01a9643f14 (2026-09-02). Data as JSON: /api/errors/f94af73e329c7c8b. Report an issue: GitHub.