Summary
timeafterleak (the 28th custom analyzer, registered in cmd/linters/main.go:75, landed via #39133) flags time.After(...) used as the channel-receive in a select case enclosed by a for/range loop. Each loop iteration allocates a fresh *time.Timer that is not garbage-collected until it fires, even when another case (ctx.Done()) is selected first. The analyzer is not yet enforced in CI (.github/workflows/cgo.yml:1122 LINTER_FLAGS lists 13 flags — -httpnoctx was just added — but not -timeafterleak).
Running the analyzer's exact pattern against production reveals 3 real violations, all the canonical select { case <-ctx.Done(): case <-time.After(d): } poll/retry idiom inside a loop:
| Site |
Loop |
Why it leaks |
pkg/cli/docker_images.go:199 |
for attempt := ... retry loop w/ exponential backoff |
new timer every retry; ctx.Done() path leaks the pending timer |
pkg/cli/add_interactive_workflow.go:41 |
for i := range 5 status poll |
new timer every poll iteration |
pkg/cli/mcp_inspect_mcp_scripts_server.go:78 |
for time.Now().Before(deadline) readiness poll |
new timer every poll iteration |
Two other time.After sites are correctly not flagged and need no change: pkg/cli/mcp_inspect_inspector.go:226 (select inside a go func(){}() — FuncLit boundary, no enclosing loop) and pkg/cli/update_check.go:315 (one-shot select, not in a loop).
Impact
Each affected loop holds one orphaned time.Timer per iteration until its duration elapses. With docker_images.go exponential backoff the durations grow (waitTime *= 2), so a cancelled multi-retry pull can pin several timers for tens of seconds. Low-severity but real per the analyzer's own contract, and these 3 sites block turning the linter on.
Recommendation
Minimal per-site fix (stop the timer on the preempting ctx.Done() path):
timer := time.NewTimer(d)
select {
case <-timer.C:
// continue
case <-ctx.Done():
timer.Stop()
return ctx.Err()
}
Then append -timeafterleak to the LINTER_FLAGS list at cgo.yml:1122 so the pattern stays gone. This mirrors the just-landed -httpnoctx enforce-readiness flow (#39016) and earlier tolowerequalfold/strconvparse conversions.
Validation checklist
Effort: Small–Moderate (3 mechanical conversions + 1 CI line).
Generated by Sergo R36.
Generated by 🤖 Sergo - Serena Go Expert · 237.8 AIC · ⌖ 12.9 AIC · ⊞ 5.3K · ◷
Summary
timeafterleak(the 28th custom analyzer, registered incmd/linters/main.go:75, landed via #39133) flagstime.After(...)used as the channel-receive in aselectcase enclosed by afor/rangeloop. Each loop iteration allocates a fresh*time.Timerthat is not garbage-collected until it fires, even when another case (ctx.Done()) is selected first. The analyzer is not yet enforced in CI (.github/workflows/cgo.yml:1122LINTER_FLAGSlists 13 flags —-httpnoctxwas just added — but not-timeafterleak).Running the analyzer's exact pattern against production reveals 3 real violations, all the canonical
select { case <-ctx.Done(): case <-time.After(d): }poll/retry idiom inside a loop:pkg/cli/docker_images.go:199for attempt := ...retry loop w/ exponential backoffctx.Done()path leaks the pending timerpkg/cli/add_interactive_workflow.go:41for i := range 5status pollpkg/cli/mcp_inspect_mcp_scripts_server.go:78for time.Now().Before(deadline)readiness pollTwo other
time.Aftersites are correctly not flagged and need no change:pkg/cli/mcp_inspect_inspector.go:226(select inside ago func(){}()— FuncLit boundary, no enclosing loop) andpkg/cli/update_check.go:315(one-shot select, not in a loop).Impact
Each affected loop holds one orphaned
time.Timerper iteration until its duration elapses. Withdocker_images.goexponential backoff the durations grow (waitTime *= 2), so a cancelled multi-retry pull can pin several timers for tens of seconds. Low-severity but real per the analyzer's own contract, and these 3 sites block turning the linter on.Recommendation
Minimal per-site fix (stop the timer on the preempting
ctx.Done()path):Then append
-timeafterleakto theLINTER_FLAGSlist atcgo.yml:1122so the pattern stays gone. This mirrors the just-landed-httpnoctxenforce-readiness flow (#39016) and earliertolowerequalfold/strconvparseconversions.Validation checklist
time.NewTimer+Stop()on the cancel pathmake golint-custom LINTER_FLAGS="-timeafterleak -test=false"reports zero violations-timeafterleaktocgo.yml:1122Effort: Small–Moderate (3 mechanical conversions + 1 CI line).
Generated by Sergo R36.