[repository-quality] 🎯 Repository Quality Improvement Report - Goroutine Lifecycle Hygiene #47586
Closed
Replies: 1 comment
|
This discussion was automatically closed because it expired on 2026-07-24T13:24:58.182Z.
|
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Analysis Date: 2026-07-23
Focus Area: Goroutine Lifecycle Hygiene — Unjoined Goroutines, Timer Leaks & HTTP Server Timeout Gaps
Strategy Type: Custom
Custom Area: Yes — goroutine hygiene is a natural follow-on to recent context propagation and error chain work; 5 specific production issues identified across 4 files.
Executive Summary
The repository launches 6 bare goroutines in production code (plus 1 in WASM). While most are properly handled, three patterns recur that undermine reliability: (1) fire-and-forget goroutines that are never joined and can silently swallow panics or leave resources alive, (2)
time.Aftertimer allocations in hot-ish paths that are GC-delayed leaks, and (3) HTTP server structs created without read/write timeouts, leaving connections open indefinitely when clients misbehave.The most critical gap is the WASM compile goroutine in
cmd/gh-aw-wasm/main.go, which has norecover(): a panic insidedoCompilewill crash the entire WASM runtime and leave the JavaScript Promise unresolved — a hard hang for browser users. TheCheckForUpdatesAsyncfunction fires a goroutine with no join mechanism; combined with atime.After(100ms)timer (which leaks for ~channel GC cycle) this is a minor but unnecessary resource concern.On the HTTP side,
bootstrap_profile_github_app.gocreates anhttp.Serverwith neitherReadHeaderTimeout,ReadTimeout, norWriteTimeout, meaning a slow client can hold a goroutine thread indefinitely. The MCP HTTP server is better — it hasReadHeaderTimeout— but still lacksWriteTimeoutandIdleTimeout.Full Analysis Report
Focus Area: Goroutine Lifecycle Hygiene
Current State Assessment
Production goroutine launches (non-test, non-testdata):
pkg/console/spinner.go:144pkg/cli/compile_update_check.go:67pkg/cli/update_check.go:249CheckForUpdatesAsyncpkg/cli/docker_images.go:155pkg/cli/bootstrap_profile_github_app.go:211pkg/cli/bootstrap_profile_helpers.go:361cmd/gh-aw-wasm/main.go:48Metrics Collected:
recover()time.Afterin non-deferred selecterrgroupusageFindings
Strengths
errgroup.WithContextis used correctly in the two highest-concurrency code paths (audit and MCP inspector)compile_update_check.gogoroutine uses a buffered channel close pattern, properly detected viactx.Done()docker_images.gogoroutine has explicit panic recovery and context-aware sleepspinner.gousessync.WaitGroupcorrectly with deferredDone()wgdonenotdeferredlinter is present and catches new violationsAreas for Improvement
recover(): panic indoCompileleaves JS Promise permanently unresolvedbootstrap_profile_github_app.goHTTP server has zero timeoutstime.After(100ms)inCheckForUpdatesAsync— timer channel leaks until GC; usetime.NewTimer+defer Stop()mcp_server_http.gohasReadHeaderTimeoutbut noWriteTimeoutorIdleTimeoutDetailed Analysis
WASM Goroutine (cmd/gh-aw-wasm/main.go:48)
No
recover()means any runtime panic (nil pointer, index out of range) insidedoCompilewill: (a) not callreject, leaving the Promise pending forever in the browser, and (b) crash the WASM instance. Fix: wrap body indefer func() { if r := recover(); r != nil { reject.Invoke(Error(fmt.Sprint(r))) } }().Bootstrap HTTP Server (pkg/cli/bootstrap_profile_github_app.go:208)
No
ReadHeaderTimeout,ReadTimeout, orWriteTimeout. The server is only reachable on loopback, but a misbehaving OAuth callback client could pin a goroutine for the fullbootstrapProfileManifestTimeoutduration. Should match theMCPServerHTTPTimeoutpattern frommcp_server_http.go.Timer Leak (pkg/cli/update_check.go:270)
When
ctx.Done()fires first, thetime.Afterchannel is not released until its timer fires 100ms later. Fix:timer := time.NewTimer(100 * time.Millisecond); defer timer.Stop().MCP HTTP Server WriteTimeout (pkg/cli/mcp_server_http.go:98)
ReadHeaderTimeoutis set butWriteTimeoutandIdleTimeoutare absent. For streaming MCP responsesWriteTimeoutmay be intentionally omitted, butIdleTimeoutshould be set to reclaim idle keep-alive connections.🤖 Tasks for Copilot Agent
NOTE TO PLANNER AGENT: Split the following tasks into individual work items.
Improvement Tasks
Task 1: Add panic recovery to WASM compile goroutine
Priority: High
Estimated Effort: Small
Focus Area: Goroutine Lifecycle Hygiene
Description: The goroutine in
cmd/gh-aw-wasm/main.gothat callsdoCompilehas norecover(). A panic inside the compiler leaves the JavaScript Promise permanently unresolved, hanging the browser indefinitely. Add a deferredrecover()that callsreject.Invokewith an error message derived from the panic value.Acceptance Criteria:
defer func() { if r := recover(); ... }()is added as the first deferred call in the WASM goroutine bodyreject.Invoke(js.Global().Get("Error").New(fmt.Sprintf("internal panic: %v", r)))so the JS Promise rejects cleanlymake test-unit)recoveris needed in this specific contextCode Region:
cmd/gh-aw-wasm/main.go(goroutine starting at line 48)Task 2: Add HTTP timeouts to bootstrap GitHub App server
Priority: High
Estimated Effort: Small
Focus Area: Goroutine Lifecycle Hygiene / Security
Description: The temporary HTTP server in
pkg/cli/bootstrap_profile_github_app.gofor the GitHub App manifest flow has no read or write timeouts. AddReadHeaderTimeoutandWriteTimeoutmirroring the values used bymcp_server_http.go.Acceptance Criteria:
http.Serverstruct gains at leastReadHeaderTimeoutandWriteTimeoutCode Region:
pkg/cli/bootstrap_profile_github_app.go(lines 208–213)Task 3: Fix time.After timer leak in CheckForUpdatesAsync
Priority: Medium
Estimated Effort: Small
Focus Area: Goroutine Lifecycle Hygiene
Description:
CheckForUpdatesAsyncusestime.After(100 * time.Millisecond)inside aselect. Whenctx.Done()fires first, the timer channel is not GC'd until the timer fires. Replace withtime.NewTimer+defer timer.Stop().Acceptance Criteria:
time.After(100 * time.Millisecond)replaced withtimer := time.NewTimer(100 * time.Millisecond)anddefer timer.Stop()selectcase uses<-timer.CTestCheckForUpdatesAsync_ContextCancellationand related tests still passCode Region:
pkg/cli/update_check.go(lines 268–277)Task 4: Add IdleTimeout to MCP HTTP server
Priority: Medium
Estimated Effort: Small
Focus Area: Goroutine Lifecycle Hygiene / Security
Description:
pkg/cli/mcp_server_http.gosetsReadHeaderTimeoutbut notIdleTimeout. Idle keep-alive connections consume a goroutine indefinitely. AddIdleTimeoutand document whyWriteTimeoutis intentionally omitted.Acceptance Criteria:
IdleTimeoutfield added to thehttp.ServerstructWriteTimeoutis intentionally omitted (streaming responses)IdleTimeoutvalue is a named constant or multiple ofMCPServerHTTPTimeoutCode Region:
pkg/cli/mcp_server_http.go(lines 98–105)Task 5: Add goroutine leak linter for unjoined fire-and-forget goroutines
Priority: Low
Estimated Effort: Medium
Focus Area: Goroutine Lifecycle Hygiene
Description: Add a new
goroutineleakanalyzer inpkg/linters/goroutineleak/that flags barego func()calls where the enclosing function neither returns a channel nor passes a WaitGroup/errgroup reference into the goroutine closure.Acceptance Criteria:
pkg/linters/goroutineleak/goroutineleak.goregistered inpkg/linters/doc.goandpkg/linters/linters.gopkg/linters/spec_test.goContainsExpectedAnalyzersupdatedmake test-unitpassesCode Region:
pkg/linters/(new subdirectorygoroutineleak/)📊 Historical Context
Previous Focus Areas
🎯 Recommendations
Immediate Actions (This Week)
recover()to WASM goroutine — Priority: High (browser hang risk)Short-term Actions (This Month)
time.Aftertimer leak inCheckForUpdatesAsync— Priority: MediumIdleTimeoutto MCP HTTP server — Priority: MediumLong-term Actions (This Quarter)
goroutineleakstatic analysis linter — Priority: Low📈 Success Metrics
time.Afterin non-deferred select: 2 → 0Next Steps
References:
Generated by Repository Quality Improvement Agent
All reactions