Repository: lima-vm/lima

Issue:
Lima fails to run, if deleting all the git tags before building
### Description

It chokes on the generated version, during validate:

`FATA[0001] the YAML is invalid, saved the buffer as "lima.REJECTED.yaml": can't parse builtin Lima version "edec9bc1": edec9bc1 is not in dotted-tri format`

It probably shouldn't generate an invalid version...

```Makefile
VERSION := $(shell git describe --match 'v[0-9]*' --dirty='.m' --always --tags)
```

Discussion:
@afbjorklund: There is some workaround for this in the GitHub actions:

```yaml
    - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683  # v4.2.2
      with:
        # To avoid "failed to load YAML file \"templates/experimental/riscv64.yaml\": can't parse builtin Lima version \"3f3a6f6\": 3f3a6f6 is not in dotted-tri format"
        fetch-depth: 0
```

commit 762e5108658a38f8e8e94104cccc5778b17fef88 - to avoid shallow clones, without tags

Maybe something like the Go versions? (the `v0.0.0`that is)@afbjorklund: This scenario also happens, when you don't set `pkg/version.Version` at all (i.e. run `go build`)

`FATA[0000] the YAML is invalid, saved the buffer as "lima.REJECTED.yaml": can't parse builtin Lima version "<unknown>": <unknown> is not in dotted-tri format`

Ideally there should be a state where the lima version is assumed to be "latest / greatest" (HEAD)
And then it would pass all tests, it would be greater than any existing version in the comparisions.

For sanity, you always want to use a real version - even if you have pass it to `make` as `VERSION`...@olamilekan000: a pr has been opened for this
https://github.com/lima-vm/lima/pull/3820@olamilekan000: @jandubois can you assign this to me

Solution patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 170dd0da..2b6e1571 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -27,9 +27,6 @@ jobs:
     timeout-minutes: 30
     steps:
     - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8  # v5.0.0
-      with:
-        # To avoid "failed to load YAML file \"templates/experimental/riscv64.yaml\": can't parse builtin Lima version \"3f3a6f6\": 3f3a6f6 is not in dotted-tri format"
-        fetch-depth: 0
     - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5  # v5.5.0
       with:
         go-version: 1.24.x
@@ -218,9 +215,6 @@ jobs:
         git config --global core.autocrlf false
         git config --global core.eol lf
     - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8  # v5.0.0
-      with:
-        # To avoid "can't parse builtin Lima version" errors
-        fetch-depth: 0
     - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5  # v5.5.0
       with:
         go-version: 1.24.x
@@ -246,9 +240,6 @@ jobs:
     timeout-minutes: 120
     steps:
     - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8  # v5.0.0
-      with:
-        # To avoid "failed to load YAML file \"templates/experimental/riscv64.yaml\": can't parse builtin Lima version \"3f3a6f6\": 3f3a6f6 is not in dotted-tri format"
-        fetch-depth: 0
     - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5  # v5.5.0
       with:
         go-version: 1.24.x
@@ -318,9 +309,6 @@ jobs:
         - ../hack/test-templates/test-misc.yaml  # TODO: merge net-user-v2 into test-misc
     steps:
     - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8  # v5.0.0
-      with:
-        # To avoid "failed to load YAML file \"templates/experimental/riscv64.yaml\": can't parse builtin Lima version \"3f3a6f6\": 3f3a6f6 is not in dotted-tri format"
-        fetch-depth: 0
     - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5  # v5.5.0
       with:
         go-version: 1.24.x
@@ -370,6 +358,8 @@ jobs:
     - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8  # v5.0.0
       with:
         # fetch-depth is set to 0 to let `limactl --version` print semver-ish version
+        # fetch-depth: 0 is required for Colima integration test because Colima
+        # checks Lima's version and requires proper semantic version format
         fetch-depth: 0
         ref: ${{ github.event.pull_request.head.sha }}
     - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5  # v5.5.0
@@ -415,8 +405,6 @@ jobs:
     timeout-minutes: 120
     steps:
     - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8  # v5.0.0
-      with:
-        fetch-depth: 0
     - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5  # v5.5.0
       with:
         go-version: 1.24.x
@@ -470,9 +458,6 @@ jobs:
         oldver: ["v0.15.1"]  # The default VM type was always QEMU until Lima v1.0
     steps:
     - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8  # v5.0.0
-      with:
-        # To avoid "failed to load YAML file \"templates/experimental/riscv64.yaml\": can't parse builtin Lima version \"3f3a6f6\": 3f3a6f6 is not in dotted-tri format"
-        fetch-depth: 0
     - name: Fetch homebrew-core commit messages
       uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8  # v5.0.0
       with:
@@ -515,8 +500,6 @@ jobs:
         - default.yaml
     steps:
     - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8  # v5.0.0
-      with:
-        fetch-depth: 0
     - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5  # v5.5.0
       with:
         go-version: 1.24.x
@@ -550,8 +533,6 @@ jobs:
     timeout-minutes: 30
     steps:
     - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8  # v5.0.0
-      with:
-        fetch-depth: 0
     - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5  # v5.5.0
       with:
         go-version: 1.24.x
@@ -577,9 +558,6 @@ jobs:
     timeout-minutes: 30
     steps:
     - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8  # v5.0.0
-      with:
-        # To avoid "failed to load YAML file \"templates/experimental/riscv64.yaml\": can't parse builtin Lima version \"3f3a6f6\": 3f3a6f6 is not in dotted-tri format"
-        fetch-depth: 0
     - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5  # v5.5.0
       with:
         go-version: 1.24.x
@@ -592,9 +570,6 @@ jobs:
     timeout-minutes: 30
     steps:
     - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8  # v5.0.0
-      with:
-        # To avoid "failed to load YAML file \"templates/experimental/riscv64.yaml\": can't parse builtin Lima version \"3f3a6f6\": 3f3a6f6 is not in dotted-tri format"
-        fetch-depth: 0
     - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5  # v5.5.0
       with:
         go-version: 1.24.x
diff --git a/pkg/limayaml/validate.go b/pkg/limayaml/validate.go
index e09f56b3..10157b4c 100644
--- a/pkg/limayaml/validate.go
+++ b/pkg/limayaml/validate.go
@@ -40,12 +40,10 @@ func Validate(y *LimaYAML, warn bool) error {
 		if _, err := versionutil.Parse(*y.MinimumLimaVersion); err != nil {
 			errs = errors.Join(errs, fmt.Errorf("field `minimumLimaVersion` must be a semvar value, got %q: %w", *y.MinimumLimaVersion, err))
 		}
-		limaVersion, err := versionutil.Parse(version.Version)
-		if err != nil {
-			errs = errors.Join(errs, fmt.Errorf("can't parse builtin Lima version %q: %w", version.Version, err))
-		}
-		if limaVersion != nil && versionutil.GreaterThan(*y.MinimumLimaVersion, limaVersion.String()) {
-			errs = errors.Join(errs, fmt.Errorf("template requires Lima version %q; this is only %q", *y.MinimumLimaVersion, limaVersion.String()))
+		// Unparsable version.Version (like commit hashes or "<unknown>") is treated as "latest/greatest"
+		// and will pass all version comparisons, allowing development builds to work.
+		if !versionutil.GreaterEqual(version.Version, *y.MinimumLimaVersion) {
+			errs = errors.Join(errs, fmt.Errorf("template requires Lima version %q; this is only %q", *y.MinimumLimaVersion, version.Version))
 		}
 	}
 	if y.VMOpts.QEMU.MinimumVersion != nil {
diff --git a/pkg/version/versionutil/versionutil.go b/pkg/version/versionutil/versionutil.go
index 36efceba..f470ef77 100644
--- a/pkg/version/versionutil/versionutil.go
+++ b/pkg/version/versionutil/versionutil.go
@@ -31,7 +31,12 @@ func compare(limaVersion, oldVersion string) int {
 	if err != nil {
 		return 1
 	}
-	cmp := version.Compare(*semver.New(oldVersion))
+	// Handle Unparsable oldVersion gracefully - treat as 0.0.0 so Unparsable limaVersion is always greater
+	oldVer, err := semver.NewVersion(oldVersion)
+	if err != nil {
+		return 1
+	}
+	cmp := version.Compare(*oldVer)
 	if cmp == 0 && strings.Contains(limaVersion, "-") {
 		cmp = 1
 	}



Test patch:
diff --git a/pkg/limayaml/validate_test.go b/pkg/limayaml/validate_test.go
index 5c9d696a..390b2ec8 100644
--- a/pkg/limayaml/validate_test.go
+++ b/pkg/limayaml/validate_test.go
@@ -38,19 +38,19 @@ func TestValidateMinimumLimaVersion(t *testing.T) {
 			name:               "minimumLimaVersion greater than current version",
 			currentVersion:     "1.1.1-114-g5bf5e513",
 			minimumLimaVersion: "1.1.2",
-			wantErr:            `template requires Lima version "1.1.2"; this is only "1.1.1"`,
+			wantErr:            `template requires Lima version "1.1.2"; this is only "1.1.1-114-g5bf5e513"`,
 		},
 		{
 			name:               "invalid current version",
 			currentVersion:     "<unknown>",
 			minimumLimaVersion: "0.8.0",
-			wantErr:            `can't parse builtin Lima version "<unknown>": <unknown> is not in dotted-tri format`,
+			wantErr:            "", // Unparsable versions are treated as "latest"
 		},
 		{
 			name:               "invalid minimumLimaVersion",
 			currentVersion:     "1.1.1-114-g5bf5e513",
 			minimumLimaVersion: "invalid",
-			wantErr:            "field `minimumLimaVersion` must be a semvar value, got \"invalid\": invalid is not in dotted-tri format\ntemplate requires Lima version \"invalid\"; this is only \"1.1.1\"",
+			wantErr:            "field `minimumLimaVersion` must be a semvar value, got \"invalid\": invalid is not in dotted-tri format", // Only parse error, no comparison error
 		},
 	}
 
diff --git a/pkg/version/versionutil/versionutil_test.go b/pkg/version/versionutil/versionutil_test.go
index 620efc82..89a9776c 100644
--- a/pkg/version/versionutil/versionutil_test.go
+++ b/pkg/version/versionutil/versionutil_test.go
@@ -41,3 +41,19 @@ func TestParse(t *testing.T) {
 	assert.Equal(t, v2.Minor, int64(19))
 	assert.Equal(t, v2.Patch, int64(1))
 }
+
+func TestCompareWithUnparsableOldVersion(t *testing.T) {
+	assert.Equal(t, GreaterThan("1.0.0", "invalid-version"), true)
+	assert.Equal(t, GreaterThan("0.1.0", "<unknown>"), true)
+	assert.Equal(t, GreaterThan("0.0.1", "commit-hash-abc123"), true)
+
+	assert.Equal(t, GreaterEqual("1.0.0", "invalid-version"), true)
+	assert.Equal(t, GreaterEqual("0.1.0", "<unknown>"), true)
+	assert.Equal(t, GreaterEqual("0.0.1", "commit-hash-abc123"), true)
+
+	assert.Equal(t, GreaterThan("invalid-lima-version", "1.0.0"), true)
+	assert.Equal(t, GreaterEqual("invalid-lima-version", "1.0.0"), true)
+
+	assert.Equal(t, GreaterThan("invalid-lima-version", "invalid-old-version"), true)
+	assert.Equal(t, GreaterEqual("invalid-lima-version", "invalid-old-version"), true)
+}