From b760dcfe6beb5c44e92e66253508e76c4d401d5e Mon Sep 17 00:00:00 2001 From: Luther Monson Date: Tue, 7 Jul 2026 21:29:11 -0700 Subject: [PATCH] fix(runner): select highest embedded runner version, bump to 2.335.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit findTarball() returned the FIRST embedded archive matching the OS prefix with no version awareness. When the embed dir holds more than one version — which happens whenever a RunnerVersion bump downloads the new tarball but leaves the previous one behind — ReadDir order wins, so the OLDER runner ships. GitHub deprecates old runner versions and refuses their connections ("Runner version vX is deprecated and cannot receive messages"), so every job silently fails: the runner connects, gets no work, exits 0 in ~7s, and the job sits queued forever. Bumping RunnerVersion alone never fixed this because the constant only controls the DOWNLOAD name, not extraction. Make findTarball parse the trailing version and pick the highest, so a leftover tarball can't regress the shipped runner. Also bump RunnerVersion 2.333.1 -> 2.335.1 (2.333.1 is now GitHub-deprecated; 2.335.1 is current). Verified on a live macOS host: the native runner now reports "Current runner version: '2.335.1'" and runs real jobs instead of exiting on the deprecation error. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01ChVRC9ZrvQarrctDwSy1S5 --- mage/download/download.go | 2 +- pkg/runner/runner.go | 66 +++++++++++++++++++++++++++++-- pkg/runner/runner_test.go | 82 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 145 insertions(+), 5 deletions(-) diff --git a/mage/download/download.go b/mage/download/download.go index 26a6116e..8eabc167 100644 --- a/mage/download/download.go +++ b/mage/download/download.go @@ -20,7 +20,7 @@ import ( ) const ( - RunnerVersion = "2.333.1" + RunnerVersion = "2.335.1" CNIVersion = "1.6.2" ContainerdVersion = "2.2.2" RuncVersion = "1.3.4" diff --git a/pkg/runner/runner.go b/pkg/runner/runner.go index 519e9d9d..7044e0fd 100644 --- a/pkg/runner/runner.go +++ b/pkg/runner/runner.go @@ -11,6 +11,7 @@ import ( "os" "path/filepath" goruntime "runtime" + "strconv" "strings" ) @@ -113,7 +114,16 @@ func (m *Manager) Extract() error { return nil } -// findTarball locates the runner archive in the embedded filesystem for the current OS. +// findTarball locates the runner archive in the embedded filesystem for the +// current OS, preferring the HIGHEST embedded version. +// +// Selecting by version (rather than "first prefix match") is critical: the +// embed directory can legitimately hold more than one version — a RunnerVersion +// bump downloads the new tarball but leaves the old one behind — and ReadDir +// returns them sorted, so a naive first-match ships the OLDER runner. GitHub +// deprecates old runner versions and refuses their connections, which silently +// fails every job ("Runner version vX is deprecated and cannot receive +// messages"). Always pick the newest so a leftover tarball can't regress us. func (m *Manager) findTarball() (string, error) { // Runner archives are named: actions-runner-{os}-{arch}-{version}.{ext} // os: linux, win, osx @@ -132,12 +142,21 @@ func (m *Manager) findTarball() (string, error) { return "", fmt.Errorf("reading embedded files: %w", err) } + var best string + var bestVer []int for _, e := range entries { name := e.Name() - if strings.HasPrefix(name, osPrefix) { - return "embed/" + name, nil + if !strings.HasPrefix(name, osPrefix) { + continue + } + ver := parseRunnerVersion(name) + if best == "" || compareRunnerVersions(ver, bestVer) > 0 { + best, bestVer = name, ver } } + if best != "" { + return "embed/" + best, nil + } // Fall back to first non-gitkeep file if no OS match for _, e := range entries { @@ -148,7 +167,46 @@ func (m *Manager) findTarball() (string, error) { return "embed/" + name, nil } - return "", fmt.Errorf("no runner archive found in embedded files (did you run 'make download-runner'?)") + return "", fmt.Errorf("no runner archive found in embedded files (did you run 'mage download'?)") +} + +// parseRunnerVersion extracts the numeric version components from a runner +// archive name like "actions-runner-osx-arm64-2.335.1.tar.gz" -> [2 335 1]. +// Names it can't parse return nil, which sorts lowest. +func parseRunnerVersion(name string) []int { + base := strings.TrimSuffix(strings.TrimSuffix(name, ".tar.gz"), ".zip") + idx := strings.LastIndex(base, "-") + if idx < 0 { + return nil + } + parts := strings.Split(base[idx+1:], ".") + nums := make([]int, 0, len(parts)) + for _, p := range parts { + n, err := strconv.Atoi(p) + if err != nil { + return nil + } + nums = append(nums, n) + } + return nums +} + +// compareRunnerVersions returns >0 if a>b, <0 if a 0) != (c.want > 0) || (got < 0) != (c.want < 0) || (got == 0) != (c.want == 0) { + t.Errorf("compareRunnerVersions(%v,%v) sign = %d, want sign %d", c.a, c.b, got, c.want) + } + } +} + +// TestFindTarball_PicksHighestVersion guards the real embedded FS: whatever +// versions are present, findTarball must return the newest one for this OS so a +// leftover older tarball can never ship a GitHub-deprecated runner. +func TestFindTarball_PicksHighestVersion(t *testing.T) { + m := &Manager{} + got, err := m.findTarball() + if err != nil { + t.Fatalf("findTarball: %v", err) + } + chosen := parseRunnerVersion(got) + + entries, err := runnerFS.ReadDir("embed") + if err != nil { + t.Fatalf("ReadDir: %v", err) + } + var osPrefix string + switch goruntime.GOOS { + case "windows": + osPrefix = "actions-runner-win-" + case "darwin": + osPrefix = "actions-runner-osx-" + default: + osPrefix = "actions-runner-linux-" + } + for _, e := range entries { + if !strings.HasPrefix(e.Name(), osPrefix) { + continue + } + if compareRunnerVersions(parseRunnerVersion(e.Name()), chosen) > 0 { + t.Errorf("findTarball chose %q but a newer tarball %q is embedded", got, e.Name()) + } + } +}