Skip to content

Add go-version-file-behavior input with latest-patch option - #785

Open
johnmaguire wants to merge 2 commits into
actions:mainfrom
johnmaguire:go-version-file-behavior
Open

Add go-version-file-behavior input with latest-patch option#785
johnmaguire wants to merge 2 commits into
actions:mainfrom
johnmaguire:go-version-file-behavior

Conversation

@johnmaguire

@johnmaguire johnmaguire commented Aug 18, 2026

Copy link
Copy Markdown

Description:
Adds a new go-version-file-behavior input that controls how an exact version read from go-version-file is interpreted:

  • exact (default): use the version as written. This is the current behavior and remains unchanged.
  • latest-patch: widen an exact major.minor.patch version into a ~X.Y.Z semver range, so the action resolves the newest available patch release of the same minor while keeping the file's version as a floor. For example, go 1.22.0 in go.mod resolves to the newest 1.22.x.

Because the newest patch is often not present in the runner's tool cache, latest-patch implies check-latest: the newest matching patch is resolved from the versions manifest rather than from whatever the cache happens to hold. If the manifest cannot be reached, the action emits a warning and falls back to resolving the range locally.

Some versions are always used as written and are not affected by latest-patch:

  • Bare minor versions (e.g. go 1.22), which already resolve to the latest patch release.
  • Prereleases (e.g. go1.22rc1), which have no patch series to float within.
  • An exact version pinned by a go.mod or go.work toolchain directive: the pin is deliberate and is never widened.

The behavior only applies to versions read from go-version-file, not to the go-version input, which already supports ranges directly. An unsupported value fails the action with an error on every input path. Combining latest-patch with go-download-base-url fails fast with a clear error, since version ranges cannot be resolved against a direct download URL.

Justification:
Dependencies can force an exact version into a consumer's go.mod (e.g. a module requiring go 1.22.0 propagates that directive), which silently pins CI to the oldest patch release of the minor even when newer patches with security fixes are available. There is currently no way to express "at least this version, latest patch" through go.mod.

Closes #481

Tradeoffs:
Inherent consequences of floating on the newest patch, for reviewers to weigh:

  • With cache: true, the dependency cache key includes the installed Go version, so each new patch release invalidates it: the first run after a patch release rebuilds the module and build caches from scratch. Documented in the advanced usage docs.
  • Builds are no longer reproducible over time: the same commit installs different Go versions as patches ship, which affects re-runs of old jobs and binary reproducibility checks.
  • The implied check-latest adds a manifest lookup (and usually a download) even when the runner has a satisfying Go preinstalled, and overrides an explicitly set check-latest: false. The override is logged.
  • Where the manifest is unreachable (e.g. GHES or runners without github.com access), the action warns and degrades to local resolution, which may install an older cached patch. The guarantee is only as strong as manifest reachability.
  • The go-version output reflects the resolved patch, not the version written in the file, which affects anything downstream that assumes they match.
  • On long-lived self-hosted runners, each new patch adds another tool cache entry; nothing prunes old ones.
  • The versions manifest can briefly lag go.dev after a release, so "newest patch" may be one behind for a short window.

Testing:

  • End-to-end test resolving go 1.12.16 in go.mod to 1.12.17 from the versions manifest with latest-patch, including the implied check-latest resolution
  • Test that an unreachable manifest emits a warning and falls back to the dist download path
  • Tests covering bare minors, toolchain directive pins, the exact default, go-version input precedence, the go-download-base-url conflict, and invalid input values on both input paths
  • Unit tests for the spec widening (exact versions, v-prefixed versions, bare minors, prereleases, ranges, wildcards)
  • npm run pre-checkin passes (format, lint, build, 137 tests)

By default an exact version read from go-version-file is used as
written, so a "go 1.22.0" directive pins CI to the oldest patch
release of the minor even when newer patches with security fixes
exist. Dependencies can force such an exact version into go.mod.

Setting go-version-file-behavior to latest-patch widens an exact
major.minor.patch version into a ~X.Y.Z range, resolving the newest
available patch release of the same minor while keeping the file's
version as a floor. Bare minors and prereleases pass through
unchanged. The default behavior (exact) is unchanged.
@johnmaguire
johnmaguire force-pushed the go-version-file-behavior branch from b71c866 to 71b6f89 Compare August 18, 2026 15:52
…dling

- latest-patch implies check-latest so the newest patch release comes
  from the versions manifest instead of a possibly stale runner tool
  cache, with a warning when the manifest cannot be reached
- fail fast when latest-patch is combined with a custom download base URL
- never widen an exact toolchain directive pin
- validate go-version-file-behavior on every input path
- widen v-prefixed versions and log when a version is used as written
- document dependency cache invalidation on new patch releases
@johnmaguire
johnmaguire marked this pull request as ready for review August 18, 2026 18:10
@johnmaguire
johnmaguire requested a review from a team as a code owner August 18, 2026 18:10
Copilot AI lite review requested due to automatic review settings August 18, 2026 18:10

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new go-version-file-behavior input to control whether an exact Go version read from go-version-file is treated as a strict pin (exact, default) or widened to “latest patch of the same minor” (latest-patch) via a semver ~X.Y.Z range, with corresponding runtime behavior, documentation, and tests.

Changes:

  • Introduces go-version-file-behavior input with validation and latest-patch widening logic for versions sourced from go-version-file.
  • Ensures latest-patch implies manifest-based resolution (check-latest), and adds a clear warning when the manifest can’t be used.
  • Documents the behavior and adds end-to-end + unit coverage for widening and key edge cases (bare minors, toolchain pins, invalid values, custom download base URL conflict).

Reviewed changes

Copilot reviewed 6 out of 7 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/main.ts Adds go-version-file-behavior parsing/validation, applies widening for version-file inputs, and forces check-latest when widening is applied.
src/installer.ts Extends getGo to warn when manifest resolution fails under latest-patch, returns richer parseGoVersionFile result, and adds latestPatchSpec.
action.yml Defines the new go-version-file-behavior input with default exact and describes constraints.
README.md Documents the new input in the usage snippet.
docs/advanced-usage.md Adds “Using the latest patch release” section and updates TOC.
tests/setup-go.test.ts Adds coverage for latest-patch behavior, fallbacks, conflicts, and unit tests for latestPatchSpec.
dist/setup/index.js Updates the built distribution to reflect the new input and runtime behavior.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support using a later patch release than what's in go.mod

2 participants