- Created a comprehensive pre-commit blocker report detailing GolangCI-Lint and TypeScript type check failures, including remediation steps and verification commands. - Enhanced the golangci-lint pre-commit hook to automatically rebuild the tool if a Go version mismatch is detected. - Introduced a new script `rebuild-go-tools.sh` to rebuild essential Go development tools, ensuring they are compiled with the current Go version. - Improved error handling and user feedback in the rebuilding process, providing clear instructions for manual intervention if needed. - Updated supervisor review report to reflect the successful implementation of Go version management and associated documentation.
19 KiB
Go Version Management Strategy
Status: Research & Recommendations Date: 2025-02-12 Context: Addressing Go version compatibility issues with development tools after automated Renovate upgrades
Problem Statement
Current Situation
- Go Upgrade Flow: Renovate automatically updates Go versions in
go.work,Dockerfile, and GitHub Actions workflows - Tool Compatibility Issue: Development tools (e.g., golangci-lint) built with older Go versions break when the project moves to a newer Go version
- Recent Failure: golangci-lint v2.8.0 built with Go 1.25.5 failed pre-commit checks after Go 1.26.0 upgrade
Current Installation State
$ go version
go version go1.26.0 linux/amd64
$ golangci-lint version
golangci-lint has version 2.8.0 built with go1.25.5 from e2e40021 on 2026-01-07T21:29:47Z
$ ls -la ~/sdk/
drwxr-xr-x 10 root root 4096 Dec 6 05:34 go1.25.5
drwxr-xr-x 10 root root 4096 Jan 21 18:34 go1.25.6
The Mismatch: Tools built with Go 1.25.5 encountering Go 1.26.0 runtime/stdlib changes.
Current Update Mechanism
-
Skill Runner (
.github/skills/utility-update-go-version-scripts/run.sh):- Uses
golang.org/dl/goX.Y.Z@latestto download specific versions - Installs to
~/sdk/goX.Y.Z/ - Updates symlink:
/usr/local/go/bin/go -> ~/sdk/goX.Y.Z/bin/go - Preserves old versions in
~/sdk/
- Uses
-
Install Scripts (
scripts/install-go-*.sh):- Downloads Go tarball from go.dev
- Removes existing
/usr/local/gocompletely - Extracts fresh installation
- Does not preserve old versions
Research Findings
1. Official Go Position on Version Management
Source: Go Downloads and golang.org/dl
Key Points:
- Multi-version support: Go officially supports running multiple versions via
golang.org/dl - SDK isolation: Each version installs to
$HOME/sdk/goX.Y.Z/ - Version-specific binaries: Run as
go1.25.6,go1.26.0, etc. - No interference: Old versions don't conflict with new installations
- Official recommendation: Use
go install golang.org/dl/goX.Y.Z@latest && goX.Y.Z download
Example Workflow:
# Install multiple versions
go install golang.org/dl/go1.25.6@latest
go1.25.6 download
go install golang.org/dl/go1.26.0@latest
go1.26.0 download
# Switch between versions
go1.25.6 version # go version go1.25.6 linux/amd64
go1.26.0 version # go version go1.26.0 linux/amd64
Verdict: ✅ Official support for multi-version. No third-party manager needed.
2. Version Managers (gvm, goenv, asdf)
GVM (Go Version Manager)
- Status: Last updated 2019, limited Go 1.13+ support
- Issues: Not actively maintained, compatibility problems with modern Go
- Verdict: ❌ Not recommended
goenv
- Status: Active, mimics rbenv/pyenv model
- Pros: Automatic version switching via
.go-versionfiles - Cons: Wrapper around
golang.org/dl, adds complexity - Use Case: Multi-project environments with different Go versions per project
- Verdict: ⚠️ Overkill for single-project workflows
asdf (via asdf-golang plugin)
- Status: Active, part of asdf ecosystem
- Pros: Unified tool for managing multiple language runtimes
- Cons: Requires learning asdf, heavy for Go-only use
- Use Case: Polyglot teams managing Node, Python, Ruby, Go, etc.
- Verdict: ⚠️ Good for polyglot environments, unnecessary for Go-focused projects
Conclusion: For single-project Go development, golang.org/dl is simpler and officially supported.
3. Industry Best Practices
Kubernetes Project
Source: kubernetes/kubernetes
- Approach: Pin Go version in
.go-version,Dockerfile, and CI - Multi-version: No—upgrades are coordinated across all tools simultaneously
- Tool management: Tools rebuilt on every Go upgrade
- Build reproducibility: Docker builds use specific
golang:X.Y.Zimages - Verdict: Single version, strict coordination, CI enforcement
Docker CLI
Source: docker/cli
- Approach: Single Go version in
Dockerfile,go.mod, and CI - Tool management: CI installs tools fresh on every run (no version conflicts)
- Upgrade strategy: Update Go version → rebuild all tools → test → merge
- Verdict: Single version, ephemeral CI environments
HashiCorp Projects (Terraform, Vault)
Source: hashicorp/terraform
- Approach: Pin Go version in
go.modand.go-version - Multi-version: No—single version enforced across team
- Tool management: Hermetic builds in Docker, tools installed per-build
- Upgrade strategy: Go upgrades blocked until all tools compatible
- Verdict: Single version, strict enforcement
Common Patterns Across Major OSS Projects:
- Single Go version per project (no multi-version maintenance)
- CI installs tools fresh on every run (avoids stale tool issues)
- Docker builds ensure reproducibility
- Local development: Developers expected to match project Go version
- Tool compatibility: Tools updated/rebuilt alongside Go upgrades
4. Tool Dependency Management
Problem: Pre-built Binaries vs. Source Builds
Pre-built Binary (Current Issue):
$ golangci-lint version
golangci-lint has version 2.8.0 built with go1.25.5
- Problem: Binary built with Go 1.25.5, incompatible with Go 1.26.0 stdlib
- Root cause: Go 1.26 introduced stdlib changes that break tools compiled with 1.25
Solution: Rebuild Tools After Go Upgrade
# Rebuild golangci-lint with current Go version
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
# Verify
$ golangci-lint version
golangci-lint has version 2.8.1 built with go1.26.0
Best Practice: Go Tools Should Use go install
- Why:
go installcompiles with the current Go toolchain - Result: Tools always match the active Go version
- Trade-off: Requires source compilation (slower) vs. downloading binary (faster)
5. GitHub Actions Best Practices
actions/setup-go Caching
- uses: actions/setup-go@v6
with:
go-version: ${{ env.GO_VERSION }}
cache: true # Caches Go modules and build artifacts
Implications:
- Cache keyed by Go version and
go.sum - Go version change → new cache entry (no stale artifacts)
- Tools installed in CI are ephemeral → rebuilt every run
- Verdict: ✅ No version conflict issues in CI
Recommendations
Strategy: Single Go Version + Tool Rebuild Protocol
Rationale:
- Official Go tooling (
golang.org/dl) already supports multi-version - Industry standard is single version per project
- CI environments are ephemeral (no tool compatibility issues)
- Local development should match CI
- Renovate already updates Go version in sync across all locations
Implementation Plan
Phase 1: Pre-Upgrade Tool Verification (Immediate)
Objective: Prevent pre-commit failures after Go upgrades
Changes:
-
Update Pre-commit Hook (
scripts/pre-commit-hooks/golangci-lint-fast.sh):# Add version check before running LINT_GO_VERSION=$(golangci-lint version | grep -oP 'go\K[0-9]+\.[0-9]+(?:\.[0-9]+)?') SYSTEM_GO_VERSION=$(go version | grep -oP 'go\K[0-9]+\.[0-9]+(?:\.[0-9]+)?') if [ "$LINT_GO_VERSION" != "$SYSTEM_GO_VERSION" ]; then echo "⚠️ golangci-lint Go version mismatch:" echo " golangci-lint: $LINT_GO_VERSION" echo " system Go: $SYSTEM_GO_VERSION" echo "" echo "🔧 Rebuilding golangci-lint with current Go version..." go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest fi -
Update Go Installation Skill (
.github/skills/utility-update-go-version-scripts/run.sh):# After Go version update, rebuild critical tools echo "🔧 Rebuilding development tools with Go $REQUIRED_VERSION..." # List of tools to rebuild TOOLS=( "github.com/golangci/golangci-lint/cmd/golangci-lint@latest" "golang.org/x/tools/gopls@latest" "golang.org/x/vuln/cmd/govulncheck@latest" ) for tool in "${TOOLS[@]}"; do echo " - Installing $tool" go install "$tool" || echo "⚠️ Failed to install $tool" done -
Create Tool Rebuild Script (
scripts/rebuild-go-tools.sh):#!/usr/bin/env bash set -euo pipefail echo "🔧 Rebuilding Go development tools..." echo "Current Go version: $(go version)" echo "" # Core development tools declare -A TOOLS=( ["golangci-lint"]="github.com/golangci/golangci-lint/cmd/golangci-lint@latest" ["gopls"]="golang.org/x/tools/gopls@latest" ["govulncheck"]="golang.org/x/vuln/cmd/govulncheck@latest" ["dlv"]="github.com/go-delve/delve/cmd/dlv@latest" ) for tool_name in "${!TOOLS[@]}"; do tool_path="${TOOLS[$tool_name]}" echo "Installing $tool_name..." if go install "$tool_path"; then echo "✅ $tool_name installed successfully" else echo "❌ Failed to install $tool_name" fi done echo "" echo "✅ Tool rebuild complete" echo "" echo "Installed versions:" golangci-lint version 2>/dev/null || echo " golangci-lint: not found" gopls version 2>/dev/null || echo " gopls: $(gopls version)" govulncheck -version 2>/dev/null || echo " govulncheck: not found" dlv version 2>/dev/null || echo " dlv: $(dlv version)"
Phase 2: Documentation Updates
Files to Update:
-
CONTRIBUTING.md:
### Go Version Updates When Renovate updates the Go version: 1. Pull the latest changes 2. Run the Go update skill: `.github/skills/scripts/skill-runner.sh utility-update-go-version` 3. Rebuild development tools: `./scripts/rebuild-go-tools.sh` 4. Restart your IDE's Go language server **Why?** Development tools (golangci-lint, gopls) are compiled binaries. After a Go version upgrade, these tools must be rebuilt with the new Go version to avoid compatibility issues. -
README.md (Development Setup):
### Keeping Go Tools Up-to-Date After pulling a Go version update from Renovate: ```bash # Rebuild all Go development tools ./scripts/rebuild-go-tools.shThis ensures tools like golangci-lint are compiled with the same Go version as your project.
-
New File:
docs/development/go_version_upgrades.md:- Detailed explanation of Go version management
- Step-by-step upgrade procedure
- Troubleshooting common issues
- FAQ section
Phase 3: Pre-commit Enhancement (Optional)
Auto-fix Tool Mismatches:
Add to .pre-commit-config.yaml:
repos:
- repo: local
hooks:
- id: go-tool-version-check
name: Go Tool Version Check
entry: scripts/pre-commit-hooks/check-go-tool-versions.sh
language: system
pass_filenames: false
stages: [pre-commit]
Script automatically detects and fixes tool version mismatches before linting runs.
Not Recommended: Multiple Go Versions Locally
Why Not Keep Multiple Versions?
Cons:
- Complexity: Switching between versions manually is error-prone
- Confusion: Which version is active? Are tools built with the right one?
- Disk space: Each Go version is ~400MB
- Maintenance burden: Tracking which tool needs which version
- CI mismatch: CI uses single version; local multi-version creates drift
Exception: If actively developing multiple Go projects targeting different versions
- Solution: Use
goenvorasdffor automatic version switching per project - Charon Context: Single project, single Go version is simpler
Verdict: ❌ Multiple versions add complexity without clear benefit for single-project development
Multi-Version Backward Compatibility Analysis
Should We Maintain N-1 Compatibility?
Context: Some projects maintain compatibility with previous major Go releases (e.g., supporting both Go 1.25 and 1.26)
Analysis:
- Charon is a self-hosted application, not a library
- Users run pre-built Docker images (Go version is locked in image)
- Developers should match the project's Go version
- No need to support multiple client Go versions
Recommendation: ❌ No backward compatibility requirement
If library: ✅ Would recommend supporting N-1 (e.g., Go 1.25 and 1.26) using:
//go:build go1.25
// +build go1.25
Implementation Timeline
Week 1: Critical Fixes (High Priority)
- Document the problem (this document)
- Implement pre-commit tool version check with auto-rebuild
- Update Go version update skill to rebuild tools
- Create
rebuild-go-tools.shscript
Week 2: Documentation & Education
- Update CONTRIBUTING.md with Go upgrade procedure
- Update README.md with tool rebuild instructions
- Create
docs/development/go_version_upgrades.md - Add troubleshooting section to copilot instructions
Week 3: Testing & Validation
- Test upgrade flow with next Renovate Go update
- Verify pre-commit auto-rebuild works correctly
- Document tool rebuild performance (time cost)
Decision Matrix
| Approach | Pros | Cons | Verdict |
|---|---|---|---|
| Keep multiple Go versions | Can build tools with older Go | Complexity, disk space, confusion | ❌ Not recommended |
| Use version manager (goenv/asdf) | Auto-switching, nice UX | Overhead, learning curve | ⚠️ Optional for polyglot teams |
| Single version + rebuild tools | Simple, matches CI, industry standard | Tools rebuild takes time (~30s) | ✅ Recommended |
| Pin tool versions to old Go | Tools don't break | Misses tool updates, security issues | ❌ Not recommended |
| Delay Go upgrades until tools compatible | No breakage | Blocks security patches, slows updates | ❌ Not recommended |
FAQ
Q1: Why did golangci-lint break after Go 1.26 upgrade?
A: golangci-lint was compiled with Go 1.25.5, but Go 1.26.0 introduced stdlib changes. Pre-built binaries from older Go versions can be incompatible with newer Go runtimes.
Solution: Rebuild golangci-lint with Go 1.26: go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
Q2: Should I keep old Go versions "just in case"?
A: No, unless actively developing multiple projects with different Go requirements.
Why not: Complexity, confusion, and disk space without clear benefit. If you need older Go, the official golang.org/dl mechanism makes it easy to reinstall:
go install golang.org/dl/go1.25.6@latest
go1.25.6 download
Q3: Will Renovate break my tools every time it updates Go?
A: Not with the recommended approach. The pre-commit hook will detect version mismatches and automatically rebuild tools before running linters.
Manual rebuild: ./scripts/rebuild-go-tools.sh (takes ~30 seconds)
Q4: How often does Go release new versions?
A: Go releases major versions every 6 months (February and August). Patch releases are more frequent for security/bug fixes.
Implication: Expect Go version updates 2-3 times per year from Renovate.
Q5: What if I want to test my code against multiple Go versions?
A: Use CI matrix testing:
strategy:
matrix:
go-version: ['1.25', '1.26', '1.27']
For local testing, use golang.org/dl:
go1.25.6 test ./...
go1.26.0 test ./...
Q6: Should I pin golangci-lint to a specific version?
A: Yes, but via Renovate-managed version pin:
# In Makefile or script
GOLANGCI_LINT_VERSION := v1.62.2
go install github.com/golangci/golangci-lint/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION)
Renovate can update the version pin, and CI will use the pinned version consistently.
Q7: Why doesn't CI have this problem?
A: CI environments are ephemeral. Every workflow run:
- Installs Go from scratch (via
actions/setup-go) - Installs tools with
go install(compiled with CI's Go version) - Runs tests/lints
- Discards everything
Local development has persistent tool installations that can become stale.
Risk Assessment
Risk: Developers Forget to Rebuild Tools
Likelihood: High (after automated Renovate updates) Impact: Medium (pre-commit failures, frustration) Mitigation:
- Pre-commit hook auto-detects and rebuilds tools (Phase 1)
- Clear documentation in CONTRIBUTING.md (Phase 2)
- Error messages include rebuild instructions
Risk: Tool Rebuild Takes Too Long
Likelihood: Medium (depends on tool size) Impact: Low (one-time cost per Go upgrade) Measurement:
$ time go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
real 0m28.341s # ~30 seconds
Mitigation: Acceptable for infrequent upgrades (2-3 times/year)
Risk: CI and Local Go Versions Drift
Likelihood: Low (both managed by Renovate) Impact: High (false positives/negatives in CI) Mitigation:
- Renovate updates
go.workand GitHub ActionsGO_VERSIONin sync - CI verifies Go version matches
go.work
References
Official Go Documentation
Industry Examples
Version Managers
- goenv
- asdf-golang
- gvm (archived, not recommended)
Related Resources
Next Steps
- Review this document with team/maintainers
- Approve strategy: Single Go version + tool rebuild protocol
- Implement Phase 1 (pre-commit tool version check)
- Test with next Renovate Go update (validate flow)
- Document lessons learned and adjust as needed
Appendix: Tool Inventory
| Tool | Purpose | Installation | Version Check |
|---|---|---|---|
| golangci-lint | Pre-commit linting | go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest |
golangci-lint version |
| gopls | Go language server (IDE) | go install golang.org/x/tools/gopls@latest |
gopls version |
| govulncheck | Security scanning | go install golang.org/x/vuln/cmd/govulncheck@latest |
govulncheck -version |
| dlv (Delve) | Debugger | go install github.com/go-delve/delve/cmd/dlv@latest |
dlv version |
| gotestsum | Test runner | go install gotest.tools/gotestsum@latest |
gotestsum --version |
Critical Tools: golangci-lint, gopls (priority for rebuild) Optional Tools: dlv, gotestsum (rebuild as needed)
Status: ✅ Research complete, ready for implementation Recommendation: Single Go version + tool rebuild protocol Next Action: Implement Phase 1 (pre-commit tool version check)