From b0440579f92971a8f65acb1a01efbcadf8c7661c Mon Sep 17 00:00:00 2001 From: Jahvon Dockery Date: Tue, 22 Sep 2026 13:46:14 -0400 Subject: [PATCH 1/2] ci: track the go toolchain from go.mod CI pinned go-version: "1.25.x" in every setup-go step, and setup-go sets GOTOOLCHAIN=local, so nothing could upgrade on its own. Two failures followed once dependencies moved to Go 1.26: - govulncheck's install pulls x/vuln v1.8.0, which needs go >= 1.26. The install failed, the scan ran a missing binary, and the job failed two steps later on an empty SARIF file. - The mvdan.cc/sh v3.14.1 bump raises the go directive to 1.26.0, which CI could not build at all. Reading the version from go.mod keeps the toolchain and the directive from drifting apart again. set -euo pipefail on the security scan makes an install failure fail that step with its real error instead of uploading an empty SARIF. The CI branch still exits 0 when vulnerabilities are found, so reporting is unchanged; the local branch now exits non-zero, which validate does not run. Co-Authored-By: Claude Opus 5 (1M context) --- .execs/validate.flow | 8 ++++++-- .github/workflows/ci.yaml | 14 +++++++------- .github/workflows/codeql.yaml | 2 +- .github/workflows/release.yaml | 2 +- .github/workflows/windows-ci.yml | 10 +++++----- go.mod | 2 +- 6 files changed, 21 insertions(+), 17 deletions(-) diff --git a/.execs/validate.flow b/.execs/validate.flow index ad28e4c4..deeb1c72 100644 --- a/.execs/validate.flow +++ b/.execs/validate.flow @@ -68,7 +68,7 @@ executables: export PATH="$PATH:./bin" fi - if [ "$CI" = "true" ]; then + if [ "${CI:-}" = "true" ]; then echo "Running golangci-lint with sarif output..." golangci-lint run ./... --fix --output.sarif.path lint.sarif --output.text.path stdout else @@ -82,12 +82,16 @@ executables: exec: dir: // cmd: | + # Without this, a failed install leaves the scan to run a missing binary and the step + # still "succeeds" — CI then fails two steps later on an empty SARIF file. + set -euo pipefail + if ! command -v govulncheck &> /dev/null; then echo "Installing govulncheck..." go install golang.org/x/vuln/cmd/govulncheck@latest fi - if [ "$CI" = "true" ]; then + if [ "${CI:-}" = "true" ]; then govulncheck -format sarif ./... > govuln.sarif echo "Security scan completed. Results saved to govuln.sarif" else diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 1e566e18..8d9fb356 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -21,7 +21,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v7 with: - go-version: "1.25.x" + go-version-file: go.mod # Build the flow under review and run every task with it, so CI exercises # the code as it would land on main rather than the released CLI. - name: Build flow @@ -47,7 +47,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v7 with: - go-version: "1.25.x" + go-version-file: go.mod - name: Set up Python uses: actions/setup-python@v7 with: @@ -78,7 +78,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v7 with: - go-version: "1.25.x" + go-version-file: go.mod - name: Set up Python uses: actions/setup-python@v7 with: @@ -134,7 +134,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v7 with: - go-version: "1.25.x" + go-version-file: go.mod - name: Install mockgen run: go install go.uber.org/mock/mockgen@v0.4.0 # Build the flow under review and run every task with it, so CI exercises @@ -167,7 +167,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v7 with: - go-version: "1.25.x" + go-version-file: go.mod # Build the flow under review and run every task with it, so CI exercises # the code as it would land on main rather than the released CLI. The # bootstrap binary lives in ./bin; `build binary` writes its own to .bin. @@ -200,7 +200,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v7 with: - go-version: "1.25.x" + go-version-file: go.mod - name: Set up Python uses: actions/setup-python@v7 with: @@ -222,7 +222,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v7 with: - go-version: "1.25.x" + go-version-file: go.mod # Build the flow under review and run every task with it, so CI exercises # the code as it would land on main rather than the released CLI. - name: Build flow diff --git a/.github/workflows/codeql.yaml b/.github/workflows/codeql.yaml index 3eae0bbd..6b0f8870 100644 --- a/.github/workflows/codeql.yaml +++ b/.github/workflows/codeql.yaml @@ -22,7 +22,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v7 with: - go-version: "1.25.x" + go-version-file: go.mod - name: Initialize CodeQL uses: github/codeql-action/init@v4.37.4 with: diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 0b417567..0b136797 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -29,7 +29,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v7 with: - go-version: "1.25.x" + go-version-file: go.mod - name: Set up QEMU uses: docker/setup-qemu-action@v4 - name: Set up Docker Buildx diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml index 5c303830..b1728ed8 100644 --- a/.github/workflows/windows-ci.yml +++ b/.github/workflows/windows-ci.yml @@ -24,7 +24,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v7 with: - go-version: "1.25.x" + go-version-file: go.mod cache: true - name: Set up Python uses: actions/setup-python@v7 @@ -62,7 +62,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v7 with: - go-version: "1.25.x" + go-version-file: go.mod cache: true - name: Set up Python uses: actions/setup-python@v7 @@ -103,7 +103,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v7 with: - go-version: "1.25.x" + go-version-file: go.mod cache: true - name: Build flow binary uses: flowexec/action@v1 @@ -127,7 +127,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v7 with: - go-version: "1.25.x" + go-version-file: go.mod cache: true - name: Download built binary uses: actions/download-artifact@v8 @@ -159,7 +159,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v7 with: - go-version: "1.25.x" + go-version-file: go.mod cache: true - name: Set up Python uses: actions/setup-python@v7 diff --git a/go.mod b/go.mod index 739d0c71..c39384a2 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/flowexec/flow/v2 -go 1.25.8 +go 1.26.0 require ( charm.land/bubbletea/v2 v2.0.9 From 84fa2d444a7f59927f8d3785406d5c3e5859bb40 Mon Sep 17 00:00:00 2001 From: Jahvon Dockery Date: Tue, 22 Sep 2026 13:57:52 -0400 Subject: [PATCH 2/2] ci: build golangci-lint from source instead of downloading it The pinned v2.5.0 release binary is built upstream with go1.25, and golangci-lint refuses to run when its own build's language version trails the targeted one - so raising the go directive to 1.26.0 broke the lint job: can't load config: the Go language version (go1.25) used to build golangci-lint is lower than the targeted Go version (1.26.0) go run builds it with this repo's toolchain, so the two cannot diverge again, and it pins one version for CI and local runs instead of deferring to whatever is on PATH. That gap is why this stayed hidden: local runs used 2.7.2 built with go1.26 while CI ran a 2.5.0 binary built with go1.25. The version stays at v2.5.0, which reports no issues. Later releases are clean to adopt but surface 57 new findings, which belong in their own change. install tools no longer installs golangci-lint, leaving one pin rather than two to keep in step. Co-Authored-By: Claude Opus 5 (1M context) --- .execs/tools.flow | 5 ----- .execs/validate.flow | 17 ++++++++++------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/.execs/tools.flow b/.execs/tools.flow index 06da0127..6f953241 100644 --- a/.execs/tools.flow +++ b/.execs/tools.flow @@ -25,11 +25,6 @@ executables: echo "goreleaser is not installed. Installing..." go install github.com/goreleaser/goreleaser/v2@v2.12.5 fi - - cmd: | - if ! command -v golangci-lint &> /dev/null; then - echo "golangci-lint is not installed. Installing..." - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s v2.5.0 - fi - cmd: | if ! command -v ginkgo &> /dev/null; then echo "ginkgo is not installed. Installing..." diff --git a/.execs/validate.flow b/.execs/validate.flow index deeb1c72..c9c88b49 100644 --- a/.execs/validate.flow +++ b/.execs/validate.flow @@ -62,17 +62,20 @@ executables: - cmd: go fmt ./... - cmd: go mod tidy - cmd: | - if ! command -v golangci-lint &> /dev/null; then - echo "Installing golangci-lint..." - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s v2.5.0 - export PATH="$PATH:./bin" - fi + set -euo pipefail + + # go run builds the linter with this repo's toolchain, so its language version always + # matches the go directive in go.mod. A prebuilt release binary carries whatever Go + # upstream built it with, and golangci-lint refuses to run once that trails the + # directive. This also pins one version for CI and local runs alike, rather than + # deferring to whatever happens to be on PATH. + lint="go run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.5.0" if [ "${CI:-}" = "true" ]; then echo "Running golangci-lint with sarif output..." - golangci-lint run ./... --fix --output.sarif.path lint.sarif --output.text.path stdout + $lint run ./... --fix --output.sarif.path lint.sarif --output.text.path stdout else - golangci-lint run ./... --fix + $lint run ./... --fix fi - verb: scan