The --go flag must match the module's go directive (and the toolchain setup-go installs). Mismatch makes the linter's go/packages load fail with go.mod requires go >= 1.26 (running go 1.24). Alternative (belt-and-suspenders): drop --go entirely — golangci-lint derives the language version from go.mod.
Three one-line mechanical fixes to foreman-direct, each verified independently.
--go pin out of sync with go.mod.github/workflows/lint.yml (or wherever the golangci-lint action is configured):
# before — --go=1.24 pinned, but go.mod declares `go 1.26` and deps require go1.25+
- uses: golangci/golangci-lint-action@v6
with:
version: v1.64.8
args: --go=1.24 --timeout=5m
# after — bump to 1.26, matching go.mod
- uses: golangci/golangci-lint-action@v6
with:
version: v1.64.8
args: --go=1.26 --timeout=5m
The --go flag must match the module's go directive (and the toolchain setup-go installs). Mismatch makes the linter's go/packages load fail with go.mod requires go >= 1.26 (running go 1.24). Alternative (belt-and-suspenders): drop --go entirely — golangci-lint derives the language version from go.mod.
# before — bogus placeholder digest: buildx rejects the pull ("invalid checksum digest format" / digest mismatch)
FROM ghcr.io/aquasecurity/trivy:0.61.1@sha256:deadbeef... AS trivy
# after — remove the pin; resolve by tag via registry
FROM ghcr.io/aquasecurity/trivy:0.61.1 AS trivy
If a real pin is later desired, obtain the genuine digest (docker buildx imagetools inspect ghcr.io/aquasecurity/trivy:0.61.1 --format '{{.Manifest.Digest}}') rather than inventing one.
sanitizeProxyPath — filepath.Clean corrupts URL paths on Windowsfilepath.Clean is OS-specific: on Windows it rewrites every / to \, mangling URL paths (/api/v1/../users → \api\users). The path package is platform-independent and always uses /. The package must be aliased because the function parameter is named path and would shadow it:
package proxy
import (
"strings"
pathpkg "path" // alias: "path" is shadowed by the parameter name below
)
// sanitizeProxyPath cleans a URL path for backend resolution.
func sanitizeProxyPath(path string) string {
if path == "" {
return "" // path.Clean("") == "." is not a valid URL path
}
cleaned := pathpkg.Clean(path) // NOT filepath.Clean — no Windows backslash corruption
if strings.HasSuffix(path, "/") && !strings.HasSuffix(cleaned, "/") && cleaned != "/" {
cleaned += "/" // preserve trailing slash: /api/ vs /api may route differently
}
return cleaned
}
The essential fix is the single line pathpkg.Clean(path); the empty-string and trailing-slash guards are optional hardening (edge cases below).
All evidence was reproduced locally with Go 1.26.0 / golangci-lint v1.64.8.
**Fix 3 — reproduced deterministically on Linux.** The Go stdlib documents the Windows behavior (`path/filepath` Clean: "any occurrences of slash are replaced by Separator", `\\` on Windows; `internal/filepathlite/path_windows.go`: `Separator = '\\'`, `IsPathSeparator` accepts both `\` and `/`). I ported that exact Windows algorithm into a test and ran a 4-test suite (`go test -count=1 -v`, all PASS, `go vet` clean, `GOOS=windows GOARCH=amd64 go build`/`vet` clean):
```
--- PASS: TestSanitizeProxyPathURLSemantics (12 URL cases)
--- PASS: TestNoBackslashes (regression: output must never contain '\')
--- PASS: TestMatchesPathClean (pins pure path semantics)
--- PASS: TestWindowsFilepathWouldCorruptURL (proves old code's corruption)
old filepath.Clean on Windows would produce "\api\users" (corrupted URL)
old filepath.Clean on Windows would produce "\api\v1\users" (corrupted URL)
```
Side-by-side for the fixed code:
| input | `filepath.Clean` on Windows (old) | `path.Clean` (new) |
|---|---|---|
| `/api/v1/../users` | `\api\users` ❌ | `/api/users` ✅ |
| `/api/v1/users` | `\api\v1\users` ❌ | `/api/v1/users` ✅ |
| `/foo/bar/` | `\foo\bar` ❌ | `/foo/bar` ✅ (with guard: `/foo/bar/`) |
| `/a//b/./c` | `\a\b\c` ❌ | `/a/b/c` ✅ |
Edge cases covered: empty string (`""` stays `""`, not `"."`), trailing slash preserved, duplicate slashes collapsed, `.`/`..` resolved, root `/` untouched, relative paths (`a/b`) pass through.
**Fix 1 — reproduced the exact CI failure and its cure.** Module with `go 1.26` + `golang.org/x/tools v0.36.0` (requires `go 1.25.0`):
```
$ GOTOOLCHAIN=go1.24.0 golangci-lint run --go=1.24 ./...
level=error msg="Running error: context loading failed: ... go: go.mod requires go >= 1.26 (running go 1.24.0; GOTOOLCHAIN=go1.24.0)"
exit: 3 # broken pin → CI red
$ golangci-lint run --go=1.26 ./... # toolchain 1.26 + --go=1.26 matching go.mod
exit: 0 # fixed → CI green
```
**Fix 2 — analysis + format validation (no Docker daemon in this sandbox).** Verified the fake digest fails buildx's checksum validation in two ways: `sha256:deadbeef` is not valid sha256 hex (buildx: "invalid checksum digest format"); `sha256:0000…0`/all-`f` placeholders parse but never match the published manifest (buildx: digest mismatch / manifest not found). Removing the `@sha256:...` suffix drops checksum validation and resolves by tag. CI confirmation requires `docker buildx build .` on a host with a daemon (was the CI re-run in the original incident). If a real pin is wanted, compute it with `docker buildx imagetools inspect` instead of inventing one.
---{"model": "deepseek-v4-flash", "problem_class": "go-ci-restore-golangci-version", "result": "passed", "tests": 4}