ADR-009: Version Pinning Strategy for Tool Management¶
Status: Accepted Date: 2026-02-04 Deciders: Project maintainers Related: v1.8.0 Build Reproducibility, ROADMAP.md Batch 4
Context¶
Our dotfiles repository uses mise to manage CLI tools, languages, and development utilities. Currently, all tools use "latest" versions (except node which uses "lts"). This provides simplicity and ensures we stay current, but creates challenges:
- Non-Deterministic Builds: CI runs and local environments may resolve to different tool versions over time
- Breaking Changes: Automatic updates can introduce breaking changes without warning
- Debugging Difficulty: Inconsistent tool versions across environments complicate troubleshooting
- Security Audit Trail: No clear record of which versions were validated/approved
We evaluated three approaches to version management:
Option A: Full Reproducibility (mise.lock)¶
Generate and commit mise.lock with exact versions of all tools.
Pros: - Complete determinism - exact same versions everywhere - Full audit trail of tool changes - Easy rollback to known-good state
Cons: - 40+ Renovate PRs per week (one per tool update) - Large repository bloat from lockfile churn - Maintenance burden reviewing minor updates - Delayed security patches (requires PR + merge)
Option B: Pin Critical Tools Only (Chosen)¶
Pin language runtimes and critical dependencies to major.minor versions; keep utilities at "latest".
Pros: - Balanced reproducibility for build-critical tools - Manageable update volume (~5 PRs/month) - Security patches auto-apply (patch versions update freely) - Development tools stay current
Cons: - Partial reproducibility (utilities may drift) - Requires judgment on what qualifies as "critical" - Some non-determinism in CI over time
Option C: Document "Latest" Philosophy¶
Keep current approach, document trade-offs clearly.
Pros: - Zero maintenance overhead - Always current with latest features/fixes - Simple mental model
Cons: - CI failures from tool updates hard to diagnose - No reproducibility guarantees - Breaking changes surprise developers
Decision¶
We choose Option B: Pin Critical Tools Only.
What Gets Pinned¶
Critical Tools (Pinned to major.minor): - node: lts (already pinned - Node.js LTS releases) - python: 3.13 (stable branch, security updates auto-apply) - rust: 1.93 (stable channel, patch updates allowed)
Rationale: These tools affect build reproducibility, compilation, and runtime behavior. Pinning ensures consistent environments across development, CI, and production deployments.
Non-Critical Tools (Stay "latest"): - CLI utilities: bat, eza, fd, ripgrep, fzf, etc. - Development tools: lazygit, neovim, glow - Linters/formatters: shellcheck, shfmt, yamllint
Rationale: These tools don't affect build artifacts or runtime behavior. Keeping them current provides latest features/fixes without reproducibility concerns.
Version Update Policy¶
- Patch versions (x.y.Z): Auto-update immediately
- Handled by mise's version resolution
-
Security patches applied without PR
-
Minor versions (x.Y.z): Renovate PRs, automerge if CI passes
- Configured in
.github/renovate.json -
Example: python 3.13.x → 3.14.x
-
Major versions (X.y.z): Manual review required
- Breaking changes expected
- Requires testing and documentation updates
Renovate Configuration¶
Update .github/renovate.json to: - Group pinned tool updates together - Automerge minor/patch for pinned tools if CI passes - Separate major version updates for manual review
Consequences¶
Positive¶
✅ Reproducible Builds: Critical language tools have consistent versions ✅ Manageable Updates: ~5 PRs/month vs 40+ with full lockfile ✅ Security Responsive: Patch versions auto-apply via mise resolution ✅ Developer Experience: Utilities stay current with latest features ✅ CI Reliability: Fewer surprise failures from tool updates ✅ Audit Trail: Git history shows when critical tools were updated
Negative¶
⚠️ Partial Non-Determinism: CLI utilities may drift slightly between environments ⚠️ Judgment Calls: Requires ongoing decisions about what's "critical" ⚠️ Mixed Strategy: Developers must remember which tools are pinned
Mitigation Strategies¶
- Document pinned vs unpinned in mise.toml comments
- CI validation ensures pinned versions work together
- Doctor task can warn about unexpected version mismatches
- Re-evaluate annually which tools qualify as critical
Implementation¶
-
Update
mise.toml:# Pinned for reproducibility python = "3.13" # Stable branch (3.13.x patches auto-update) rust = "1.93" # Stable channel (1.93.x patches auto-update) node = { version = "lts", postinstall = "corepack enable" } # Already pinned # Keep at latest for freshness neovim = "latest" "github:sharkdp/bat" = "latest" # ... etc -
Update version policy comment in mise.toml header
-
Test CI validates pinned versions
-
Monitor Renovate PR volume over next month
Alternatives Considered¶
- Pin Go/Lua/Zig if added: Future languages should follow same pinning pattern
- mise.lock for CI only: Complexity outweighs benefits (two config systems)
- Pin by ecosystem: All npm tools pinned, all cargo tools unpinned (rejected: too coarse)
References¶
- mise Version Syntax
- Renovate Grouping
- ROADMAP.md: v1.8.0 Build Reproducibility
- Implementation Plan (archived): docs/roadmaps/archive/10-legacy-implementation-plans.md (Batch 4)
Review Schedule¶
Next Review: 2027-02-04 (1 year) Triggers for Early Review: - Adding new language runtimes - CI failure rate increases >10% due to tool updates - Team feedback indicates pinning strategy is too loose/strict