Skip to content

Commit ff0bca9

Browse files
mpywclaude
andcommitted
fix!: run goroutine and goroutinederive checkers independently
BREAKING CHANGE: Previously, setting -goroutine-deriver would replace the goroutine checker entirely. Now both checkers run independently, allowing users to get both "context not propagated" and "deriver not called" diagnostics simultaneously. This is the intended behavior since the two checkers serve different purposes: - goroutine: ensures context is captured/used in goroutines - goroutinederive: ensures specific deriver functions are called Retract all previous versions (v0.1.1-v0.2.0) due to this bug. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 4339aaa commit ff0bca9

File tree

4 files changed

+14
-10
lines changed

4 files changed

+14
-10
lines changed

analyzer.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -124,12 +124,12 @@ func runASTChecks(
124124
goStmtCheckers []checkers.GoStmtChecker
125125
)
126126

127-
// When goroutine-deriver is set, it replaces the base goroutine checker.
128-
// The derive checker is a more specific version that checks for deriver function calls.
127+
if enableGoroutine {
128+
goStmtCheckers = append(goStmtCheckers, goroutine.New())
129+
}
130+
129131
if goroutineDeriver != "" {
130132
goStmtCheckers = append(goStmtCheckers, goroutinederive.New(goroutineDeriver))
131-
} else if enableGoroutine {
132-
goStmtCheckers = append(goStmtCheckers, goroutine.New())
133133
}
134134

135135
if enableWaitgroup {

go.mod

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ module github.com/mpyw/goroutinectx
22

33
go 1.24.0
44

5+
// Retract all previous versions due to a bug where -goroutine-deriver flag
6+
// incorrectly disabled the base goroutine checker instead of running both independently.
7+
retract [v0.1.1, v0.2.0]
8+
59
require golang.org/x/tools v0.40.0
610

711
require (

testdata/src/goroutinederiveand/evil.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ func badAndNested2LevelInnerMissingOneDeriver(ctx context.Context, txn *newrelic
6767
//
6868
// AND - both derivers in nested IIFE (not at outer level).
6969
func goodAndBothDeriverInNestedIIFE(ctx context.Context, txn *newrelic.Transaction) {
70-
go func() { // want "goroutine should call github.com/newrelic/go-agent/v3/newrelic.Transaction.NewGoroutine\\+github.com/newrelic/go-agent/v3/newrelic.NewContext to derive context"
70+
go func() { // want `goroutine does not propagate context "ctx"` `goroutine should call github.com/newrelic/go-agent/v3/newrelic.Transaction.NewGoroutine\+github.com/newrelic/go-agent/v3/newrelic.NewContext to derive context`
7171
func() {
7272
txn = txn.NewGoroutine()
7373
ctx = newrelic.NewContext(ctx, txn)
@@ -80,7 +80,7 @@ func goodAndBothDeriverInNestedIIFE(ctx context.Context, txn *newrelic.Transacti
8080
//
8181
// AND - split derivers across levels (outer has first, IIFE has second).
8282
func goodAndSplitDeriversAcrossLevels(ctx context.Context, txn *newrelic.Transaction) {
83-
go func() { // want "goroutine should call github.com/newrelic/go-agent/v3/newrelic.Transaction.NewGoroutine\\+github.com/newrelic/go-agent/v3/newrelic.NewContext to derive context"
83+
go func() { // want `goroutine does not propagate context "ctx"` `goroutine should call github.com/newrelic/go-agent/v3/newrelic.Transaction.NewGoroutine\+github.com/newrelic/go-agent/v3/newrelic.NewContext to derive context`
8484
txn = txn.NewGoroutine() // First deriver at outer level
8585
func() {
8686
ctx = newrelic.NewContext(ctx, txn) // Second deriver in IIFE - not counted for outer
@@ -94,7 +94,7 @@ func goodAndSplitDeriversAcrossLevels(ctx context.Context, txn *newrelic.Transac
9494
//
9595
// AND - nested 3-level, outer only has first deriver.
9696
func badAndNested3LevelOuterPartial(ctx context.Context, txn *newrelic.Transaction) {
97-
go func() { // want "goroutine should call github.com/newrelic/go-agent/v3/newrelic.Transaction.NewGoroutine\\+github.com/newrelic/go-agent/v3/newrelic.NewContext to derive context"
97+
go func() { // want `goroutine does not propagate context "ctx"` `goroutine should call github.com/newrelic/go-agent/v3/newrelic.Transaction.NewGoroutine\+github.com/newrelic/go-agent/v3/newrelic.NewContext to derive context`
9898
txn = txn.NewGoroutine() // Only first deriver
9999
go func() { // want "goroutine should call github.com/newrelic/go-agent/v3/newrelic.Transaction.NewGoroutine\\+github.com/newrelic/go-agent/v3/newrelic.NewContext to derive context"
100100
ctx = newrelic.NewContext(ctx, txn) // Only second deriver

testdata/src/goroutinederivemixed/evil.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ func goodMixedNested2LevelInnerSatisfiesNeither(ctx context.Context, txn *newrel
6464
//
6565
// IIFE pattern with AND group deriver requirements.
6666
func goodMixedSplitDeriversAcrossLevels(ctx context.Context, txn *newrelic.Transaction) {
67-
go func() { // want "goroutine should call github.com/newrelic/go-agent/v3/newrelic.Transaction.NewGoroutine\\+github.com/newrelic/go-agent/v3/newrelic.NewContext,github.com/my-example-app/telemetry/apm.NewGoroutineContext to derive context"
67+
go func() { // want `goroutine does not propagate context "ctx"` `goroutine should call github.com/newrelic/go-agent/v3/newrelic.Transaction.NewGoroutine\+github.com/newrelic/go-agent/v3/newrelic.NewContext,github.com/my-example-app/telemetry/apm.NewGoroutineContext to derive context`
6868
txn = txn.NewGoroutine() // Only first of AND
6969
func() {
7070
ctx = newrelic.NewContext(ctx, txn) // Second of AND in IIFE - doesn't count
@@ -78,7 +78,7 @@ func goodMixedSplitDeriversAcrossLevels(ctx context.Context, txn *newrelic.Trans
7878
//
7979
// Satisfies the mixed requirement via OR alternative path.
8080
func goodMixedOrAlternativeInNestedIIFE(ctx context.Context) {
81-
go func() { // want "goroutine should call github.com/newrelic/go-agent/v3/newrelic.Transaction.NewGoroutine\\+github.com/newrelic/go-agent/v3/newrelic.NewContext,github.com/my-example-app/telemetry/apm.NewGoroutineContext to derive context"
81+
go func() { // want `goroutine does not propagate context "ctx"` `goroutine should call github.com/newrelic/go-agent/v3/newrelic.Transaction.NewGoroutine\+github.com/newrelic/go-agent/v3/newrelic.NewContext,github.com/my-example-app/telemetry/apm.NewGoroutineContext to derive context`
8282
func() {
8383
ctx = apm.NewGoroutineContext(ctx)
8484
_ = ctx
@@ -90,7 +90,7 @@ func goodMixedOrAlternativeInNestedIIFE(ctx context.Context) {
9090
//
9191
// Nested pattern where outer only calls first deriver of AND group.
9292
func badMixedNested3LevelOuterPartial(ctx context.Context, txn *newrelic.Transaction) {
93-
go func() { // want "goroutine should call github.com/newrelic/go-agent/v3/newrelic.Transaction.NewGoroutine\\+github.com/newrelic/go-agent/v3/newrelic.NewContext,github.com/my-example-app/telemetry/apm.NewGoroutineContext to derive context"
93+
go func() { // want `goroutine does not propagate context "ctx"` `goroutine should call github.com/newrelic/go-agent/v3/newrelic.Transaction.NewGoroutine\+github.com/newrelic/go-agent/v3/newrelic.NewContext,github.com/my-example-app/telemetry/apm.NewGoroutineContext to derive context`
9494
txn = txn.NewGoroutine() // Only first of AND
9595
go func() { // want "goroutine should call github.com/newrelic/go-agent/v3/newrelic.Transaction.NewGoroutine\\+github.com/newrelic/go-agent/v3/newrelic.NewContext,github.com/my-example-app/telemetry/apm.NewGoroutineContext to derive context"
9696
ctx = newrelic.NewContext(ctx, txn) // Only second of AND

0 commit comments

Comments
 (0)