Releases: mpyw/goroutinectx
Release list
v0.12.0
goroutinectx can now run inside golangci-lint, as a module plugin. Nothing changes for the command or go vet.
golangci-lint
goroutinectx is not bundled with golangci-lint, so build a binary that holds it. (#77)
.custom-gcl.yml:
version: v2.13.1 # the golangci-lint release to build
plugins:
- module: github.com/mpyw/goroutinectx
import: github.com/mpyw/goroutinectx/plugin
version: v0.12.0.golangci.yml:
version: "2"
linters:
enable:
- goroutinectx
settings:
custom:
goroutinectx:
type: module
description: Checks goroutine context propagation.
settings:
goroutine-deriver: github.com/my-example-app/telemetry/apm.NewGoroutineContext
context-carriers:
- github.com/labstack/echo/v4.Context
external-spawner:
- github.com/example/workerpool.Pool.Submit
spawnerlabel: truegolangci-lint custom # writes ./custom-gcl
./custom-gcl run ./...The keys under settings are the flag names. A key left out keeps its default, and an unknown key stops the run.
| Key | Flag | Setting |
|---|---|---|
goroutine-deriver |
A string | The same string. , means OR and + means AND, so it stays one string |
external-spawner, context-carriers |
A comma-separated string | A list, one name per item |
Each checker, such as goroutine or spawnerlabel |
A bool | A bool |
Other changes
- The run logic moves from
analyzer.gotointernal/run, so the flags and the plugin build the same configuration. The flags, their defaults, andErrNoInspectorare unchanged. (#77) - The agent skills vendored from declscope are updated to 0.18.0.
Install
mise use "github:mpyw/goroutinectx@0.12.0"Full changelog: v0.11.0...v0.12.0
Changelog
v0.11.0
//goroutinectx:ignore is now read strictly: a reason goes after //, and a name goroutinectx does not know is reported. An ignore that silences nothing is now reported as unused, as it was meant to be.
Important
goroutinectx now requires Go 1.27. It required 1.26 before.
Some reports can appear in code that has not changed. Each points at an ignore or directive that did nothing:
- an ignore on a line with no violation, now reported as unused;
- an unknown checker or directive name, such as
//goroutinectx:ignore gorutine; //goroutinectx:ignore errgroupon a conc call. Useconcthere.
//goroutinectx:ignore
(#66)
| Comment | 0.10.0 | 0.11.0 |
|---|---|---|
//goroutinectx:ignore // why |
Silenced nothing: // why was read as a checker |
Ignores every checker |
//goroutinectx:ignore goroutine - why |
Ignores goroutine |
The same. - why is kept for compatibility |
//goroutinectx:ignore intentionally detached |
Silenced nothing, reported as unused | unknown checker "intentionally detached", with the valid names |
//goroutinectx:ignore goroutine,typo |
Ignored goroutine |
Reported, and ignores nothing |
//goroutinectx:ignore conc |
Silenced nothing | Ignores the conc checker |
//goroutinectx:ignore errgroup on a conc call |
Silenced it | Reported as unused. Use conc |
//goroutinectx:ignre |
Did nothing, silently | unknown directive goroutinectx:ignre |
An ignore counts as used only when it silences a report. The check used to consult the ignore before it ran. So an ignore on a clean line was never reported as unused.
//line directives
Ignores, spawner directives and the generated-file skip were keyed by names and lines that a //line directive changes. (#63)
| Case | 0.10.0 | 0.11.0 |
|---|---|---|
An ignore below //line |
Did not silence, and was reported unused | Silences |
A generated file with //line |
Analyzed and reported | Skipped |
Two //line regions whose lines touch |
A spawner directive could attach to the wrong function | Lines are those in the file |
Performance
Looking up what was assigned to a variable walked the whole file, and rebuilt an inspector, on every lookup. It now uses the pass inspector. On a file with many closure reassignments, a run fell from 18.5 s to 1.2 s of CPU. On typical code the difference is within noise. (#64)
Other changes
- Code reads more directly. Duplicated closure checks and a duplicated package match are shared, and needless nil checks are gone. Output is unchanged. (#68, #69, #70, #71)
- The agent skills vendored from declscope are updated to 0.15.0, which adds
declscope-authoring. (#72)
Install
mise use "github:mpyw/goroutinectx@0.11.0"Full changelog: v0.10.0...v0.11.0
Changelog
- 27a900d Bump declscope version
- 340d3e3 chore(deps): bump declscope to 0.12.0, and turn on rules.unused: strict (#56)
- a94d68a fix: read //goroutinectx:ignore as declscope reads its ignore (#66)
- a6f23f2 fix: use unadjusted positions so //line directives do not move directives (#63)
- 29dee91 perf: look up assignments with the pass inspector (#64)
- 5a3fb0b refactor: adopt Go 1.27 syntax and require Go 1.27
- 21c7716 refactor: drop nil checks that a comma-ok assertion already makes (#71)
- ab051c1 refactor: reuse the closure checks and the package path match (#70)
- c89cfa0 refactor: say searches and splits with slices and strings (#68)
- bcf3f7b refactor: walk assignments through two iterators in probe (#69)
v0.10.0
Directives are now read exactly as Go reads them, through go/ast.ParseDirective. Only the canonical form is a directive, names match exactly, and a comment that is meant as a directive but spelled differently is reported. The minimum Go version is now 1.26.
Important
This release has three breaking changes.
// goroutinectx:ignoreand// goroutinectx:spawner, with a space after//, used to work. They now have no effect and are reported.- Lookalikes such as
//goroutinectx:ignoredand//goroutinectx:spawnerXused to match. They now have no effect. - Go 1.25 is no longer supported.
See Migrating.
Only //goroutinectx:name is a directive
A directive is a line comment with no space after //, no space after the colon, and a lowercase name. The arguments of ignore read as before: a checker list, a reason after - or //, and empty arguments to ignore every checker.
//goroutinectx:ignore goroutine - the caller cancels this one
go worker()Malformed directives are reported
Any other comment whose text, after // or /*, starts with goroutinectx: once any space is skipped has no effect. It is reported with one message:
worker.go:12:1: malformed goroutinectx directive: write it as //goroutinectx:name
| Comment | 0.9.3 | 0.10.0 |
|---|---|---|
//goroutinectx:ignore |
Directive | Directive |
// goroutinectx:ignore (space or tab after //) |
Directive | Malformed |
//goroutinectx: ignore |
Ordinary comment | Malformed |
/* goroutinectx:ignore */ |
Ordinary comment | Malformed |
//goroutinectx:Ignore |
Ordinary comment | Malformed |
//goroutinectx: |
Ordinary comment | Malformed |
The same applies to spawner. The report matters most there. A spawner that has no effect means the functions passed to it are no longer checked, and nothing else would show it. Prose that mentions a directive partway through a comment is not reported.
Names match exactly
A directive used to match on its prefix, so a longer name counted:
| Comment | 0.9.3 | 0.10.0 |
|---|---|---|
//goroutinectx:ignored |
Ignore every checker | Ordinary comment |
//goroutinectx:spawnerX |
Spawner | Ordinary comment |
These are valid directive syntax with an unknown name, so they are not reported as malformed.
Go 1.26 or later
ast.ParseDirective was added in Go 1.26. The go directive in go.mod is now 1.26.0, and CI tests Go 1.26 and 1.27, the two releases Go supports. The prebuilt binaries do not need Go at all.
Migrating
Run goroutinectx 0.10.0 over the repository.
| You see | Do this |
|---|---|
malformed goroutinectx directive |
Rewrite the comment as //goroutinectx:ignore or //goroutinectx:spawner: no space after // or after the colon, a line comment, a lowercase name |
A report under //goroutinectx:ignored or another lookalike |
Use the exact name, //goroutinectx:ignore |
A new report in a function passed to a helper marked //goroutinectx:spawnerX or similar |
Use the exact name, //goroutinectx:spawner |
| The build asks for a newer Go | Build with Go 1.26 or later, or install a prebuilt binary |
Full changelog: v0.9.3...v0.10.0
v0.9.3
Changelog
v0.9.2
v0.9.1
Changelog
Enabled Immutable Releases
v0.9.0
v0.8.0
What's Changed
- refactor: use external tests and add t.Parallel() by @mpyw in #25
- fix: revert internal tests to internal package by @mpyw in #26
- [Claude] Remove MCP configuration from the repository by @mpyw in #27
- [Claude] Add Dependabot configuration by @mpyw in #28
- chore(deps): bump codecov/codecov-action from 5 to 7 by @dependabot[bot] in #30
- chore(deps): bump actions/checkout from 4 to 7 by @dependabot[bot] in #29
- chore(deps): bump golangci/golangci-lint-action from 7 to 9 by @dependabot[bot] in #31
- chore(deps): bump actions/setup-go from 5 to 6 by @dependabot[bot] in #32
- chore(deps): bump actions/setup-go from 6 to 7 by @dependabot[bot] in #35
- chore: Go 1.27 modernization (go fix) + generic method coverage by @mpyw in #36
- chore(deps)!: bump x/tools to v0.49.0, drop Go 1.24 support by @mpyw in #37
New Contributors
- @dependabot[bot] made their first contribution in #30
Full Changelog: v0.7.5...v0.8.0
v0.7.5
Bug Fixes
-
Conditional reassignment detection: Fixed false negatives when variables holding closures are conditionally reassigned
- Previously, only the last assignment was checked
- Now checks ALL assignments from the last unconditional assignment onwards
- Affects:
goroutine,errgroup,spawnercheckers
-
Factory pattern conditional reassignment: Fixed detection in factory functions that return conditionally reassigned variables
- Previously short-circuited when finding any nested func literal using context
- Now correctly checks only what's actually returned
- Added recursive context-checking for nested factories (triple higher-order patterns)
Example
func example(ctx context.Context) {
fn := func() { _ = ctx } // uses ctx
if condition {
fn = func() { /* no ctx */ } // doesn't use ctx
}
go fn() // Now correctly warns: not all paths use ctx
}Full Changelog: v0.7.4...v0.7.5
v0.7.4
What's Changed
Bug Fixes
- Fix regression where
-goroutine-deriverflag didn't work with errgroup/waitgroup/conc/spawner checkers
Improvements
- Rename
CallArgCheckertoSpawnCallbackCheckerfor clarity - Fix export consistency (unexported types with unexported fields)
- Add comprehensive tests for deriver checking with errgroup, waitgroup, and spawner
- Add probe unit tests for
funcLitOfLiteralKey - Add IIFE and nested factory test cases for improved coverage
Coverage
FactoryCallReturnsContextUsingFunc: 66.7% → 88.9%funcLitOfLiteralKey: 75.0% → 100.0%- Total probe/checkers: 85.7% → 86.6%
Note: v0.7.2 and v0.7.3 have been retracted due to the deriver regression. Please use v0.7.4 instead.