About go-modernize
A skill for incrementally updating Go code to use features supported by its module's Go directive. It covers modernization workflow, version upgrades, and migrations across language and standard-library releases.
Key features
- 01Incremental modernization modes
- 02Go-version feature reference
- 03Official modernization tool guidance
- 04Migration examples for standard-library APIs
- 05Checks for version-compatible changes
Use cases
- 01Modernizing only the code being edited
- 02Planning a Go directive upgrade
- 03Updating deprecated or older standard-library usage
- 04Running a package or module modernization sweep
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ eduardo-sl/ go-agent-skills --skill go-modernizeAfter installing, type/go-modernizein Claude Code to confirm it loaded.
Bundled files
References and scripts installed along with SKILL.md.
references/generics.mdgo1.27.mdstdlib-migrations.mdversion-bump.mdDetails
From the project READMEGo Modernize
Go evolves. Code written for Go 1.16 should not look the same as code targeting
Go 1.27. Modernize incrementally — update go.mod, then adopt new patterns.
Never adopt a feature above the go directive in go.mod. Raise the
directive deliberately, in its own commit, and only to a version the project's
CI and deployment images actually run.
Detailed reference material, loaded on demand:
references/generics.md— replacinginterface{}with type parameters, constraints, generic containers, when NOT to use generics.references/stdlib-migrations.md— before/after examples for slog, errors.Join, slices/maps helpers, range-over-int, and iterators.references/go1.27.md— generic methods,CutLast, URL/ValuesClone,big.Int.Divide, stdlibuuid, json/v2 semantics, synctest and httptest additions, tooling changes.references/version-bump.md— procedure and per-release checklist (1.25-1.27) for what can break when thegodirective is raised.
Read a reference file only when the summary below is not enough.
Operating Modes
- Inline (default — the developer is working on something else): modernize only the lines you are already changing. Note other opportunities once, at the end, each with the gain it brings; do not touch unrelated files — an unrequested sweep buries the real change in churn. If a suggestion is declined, do not raise it again in the session; if the developer wants that remembered, add one line to the project's agent-config file (CLAUDE.md, AGENTS.md).
- Version bump (raising the
godirective): a behavior change even with no source edits. Followreferences/version-bump.md— prerequisites, then the per-release breakage checklist — before adopting any feature. - Sweep (asked to modernize a package or the module): run the
procedure below and land one commit per category —
go fixoutput, deprecated APIs, language features, stdlib helpers, tests — each green on build and tests. The categories are independent; if your environment supports parallel sub-agents, give each one a category and work on a separate branch or worktree.
Priority Order
Apply in this order when time is limited:
- Correctness and security —
errors.Is/Asinstead of==,http.NewRequestWithContext,os.Rootfor user-supplied paths,crypto/randwherever randomness protects something, deprecatedx/cryptopackages replaced by their stdlib versions. - Readability —
any,min/max, range over int,slices/maps,cmp.Or,sync.OnceValue,wg.Go,errors.AsType. - Gradual — iterators, logger migration to
slog,go.modtool directives, dropping dependencies the stdlib now covers.
Modernization Procedure
-
Check the
godirective ingo.mod— it caps which features you can use. Raising it is the Version bump mode, never a side effect of a feature change. -
Run the official modernizers first — they find and fix the mechanical migrations automatically:
# Go 1.26+ — the modernizers now live in go fix go fix ./... # Go 1.25 and earlier, or a modernizer newer than your toolchain go run golang.org/x/tools/go/analysis/passes/modernize/cmd/modernize@latest -fix ./...Both rewrite source in place. Commit before running, and review the diff. If neither command is available, apply the table below manually.
-
Scan the table below for the judgment-based migrations the analyzer does not cover (generics, iterators, logger replacement) and apply them case by case.
-
Run
go build ./...and the test suite after each group of changes.
Feature Table by Go Version
| Go Version | Feature | Action |
|---|---|---|
| 1.13+ | errors.Is, errors.As | Replace == error comparisons |
| 1.13+ | http.NewRequestWithContext | Replace http.NewRequest |
| 1.16+ | embed | Replace go-bindata / packr |
| 1.18+ | Generics | Replace interface{} utility functions |
| 1.20+ | errors.Join | Replace manual error accumulation |
| 1.21+ | log/slog | Replace log for structured logging |
| 1.21+ | slices, maps | Replace hand-written slice/map utilities |
| 1.21+ | min, max builtins | Replace math.Min/math.Max (float64-only) |
| 1.21+ | sync.OnceValue, sync.OnceFunc | Replace sync.Once + package var pairs |
| 1.22+ | Range over int | Replace for i := 0; i < n; i++ |
| 1.22+ | Per-iteration loop variables | Delete v := v shadow copies (only once go.mod says 1.22+) |
| 1.22+ | math/rand/v2 | Replace math/rand; drop rand.Seed |
| 1.22+ | cmp.Or | Replace "first non-zero value" if-chains |
| 1.22+ | ServeMux method + wildcard patterns | Replace a router dependency used only for GET /users/{id} |
| 1.23+ | Range over func | Replace callback-based iteration |
| 1.23+ | unique.Make | Replace hand-rolled string interning |
| 1.24+ | for b.Loop() | Replace for range b.N in benchmarks |
| 1.24+ | t.Context() | Replace context.Background() in tests |
| 1.24+ | os.Root | Replace manual path-traversal checks |
| 1.24+ | go.mod tool directive | Replace the tools.go blank-import file |
| 1.24+ | runtime.AddCleanup | Replace runtime.SetFinalizer |
| 1.24+ | omitzero struct tag | Replace omitempty where Go zero values (e.g. time.Time{}) must be omitted |
| 1.24+ | strings.SplitSeq, strings.Lines, FieldsSeq | Replace Split when the slice is only ranged over |
| 1.24+ | crypto/sha3, crypto/hkdf, crypto/pbkdf2 | Replace the golang.org/x/crypto versions |
| 1.24+ | crypto/rand.Text | Replace hand-rolled random token encoders |
| 1.25+ | testing/synctest | Replace time.Sleep in concurrency tests |
| 1.25+ | sync.WaitGroup.Go | Replace wg.Add(1) + go func(){defer wg.Done()} |
| 1.25+ | reflect.TypeAssert | Replace v.Interface().(T) (no allocation) |
| 1.25+ | Container-aware GOMAXPROCS | Remove go.uber.org/automaxprocs |
| 1.26+ | errors.AsType | Replace errors.As with a declared target variable |
| 1.26+ | slog.NewMultiHandler | Replace hand-written fan-out handlers |
| 1.26+ | new(expr) | Replace a temp variable taken by address |
| 1.26+ | httputil.ReverseProxy.Rewrite | Replace the deprecated, unsafe Director hook |
| 1.27+ | Generic methods | Replace package-level generic helpers that belong to one type (not when an interface must expose them) |
| 1.27+ | Promoted fields as literal keys | Replace Outer{Inner: Inner{ID: x}} nesting |
| 1.27+ | strings.CutLast, bytes.CutLast | Replace LastIndex + slicing |
| 1.27+ | url.URL.Clone, url.Values.Clone | Replace hand-written deep copies |
| 1.27+ | big.Int.Divide | Replace QuoRem + sign correction |
| 1.27+ | stdlib uuid | Replace google/uuid for v4/v7 IDs |
| 1.27+ | encoding/json/v2 | Adopt per call site — stricter semantics, see reference |
| 1.27+ | synctest.Sleep, httptest.NewTestServer | Replace time.Sleep+Wait and socket-bound test servers |
| 1.27+ | testing/cryptotest.SetGlobalRandom | Replace tls.Config.Rand in tests |
Worked Migrations
Before/after examples for every row above live in
references/stdlib-migrations.md (stdlib and language features) and
references/generics.md (type parameters). Test-specific migrations —
t.Context(), b.Loop(), synctest — are owned by the go-test-quality
skill.
Verification Checklist
go.modversion matches the features used in the codebase- No
interface{}whereanyor type parameters would be clearer log/slogused instead oflog.Printffor structured loggingerrors.Joinused instead of manual error string concatenationslices.Contains,slices.SortFunc,maps.Clonereplace hand-written loops- Range over int (
for i := range n) used where applicable http.NewRequestWithContextused instead ofhttp.NewRequest- No
sort.Slice— useslices.SortFuncwithcmp.Compare - Generics used for type-safe containers and utilities, not overused for trivial cases
- Third-party dependencies evaluated against stdlib alternatives added in recent Go versions
sync.WaitGroup.Goused instead of manualAdd/Donepairs (Go 1.25+)- Tests use
t.Context()andfor b.Loop()(Go 1.24+) tools.goreplaced by thego.modtool directive (Go 1.24+)