Common Go Bugs CodeHawk Catches — Concurrency, Error Handling, and Security
Go's concurrency model is elegant. It's also the source of some of the hardest-to-debug production issues.
Goroutines look simple: go func(). But simple syntax hides complexity. Add channels, mutexes, and context cancellation, and you have a recipe for race conditions, deadlocks, and resource leaks that only show up under load.
CodeHawk is tuned to catch the Go-specific patterns that slip past code review — especially concurrency issues, error handling bugs, and the security vulnerabilities that are easy to miss in Go.
Here are the five most common Go bugs CodeHawk catches in typical HTTP services, gRPC handlers, and concurrent applications.
1. Unhandled Goroutine Panics
The most insidious Go bug: a goroutine panics, but you don't know about it because you never waited for it.
// Vulnerable — goroutine panic silently crashes
func processRequests(ch chan *Request) {
go func() {
for req := range ch {
// If handleRequest panics, this goroutine dies
handleRequest(req)
}
}()
}
// CodeHawk flags it: Unhandled panic in goroutine
// Safe: Recover and log the panic
func processRequests(ch chan *Request) {
go func() {
defer func() {
if r := recover(); r != nil {
log.Errorf("Request handler panicked: %v", r)
}
}()
for req := range ch {
handleRequest(req)
}
}()
}
In production, a goroutine panic crashes the entire goroutine silently. Your application keeps running, but a critical background job just died. You don't find out until users report missing features or data doesn't get synced.
CodeHawk looks for go func() without panic recovery and flags it.
2. Forgotten Context Cancellation
Context is Go's way of signaling cancellation and timeouts. Developers often forget to check context.Done() in loops.
// Vulnerable — ignores context cancellation
func (s *Server) handleRequest(ctx context.Context, req *Request) error {
for i := 0; i < 1000; i++ {
// No check for ctx.Done()
// If context is cancelled, this loop continues anyway
result := s.slowOperation(req)
process(result)
}
return nil
}
// CodeHawk flags it: Loop doesn't check context cancellation
// Safe: Check context.Done()
func (s *Server) handleRequest(ctx context.Context, req *Request) error {
for i := 0; i < 1000; i++ {
select {
case <-ctx.Done():
return ctx.Err()
default:
result := s.slowOperation(req)
process(result)
}
}
return nil
}
This is a resource leak disguised as correct code. Your request handler gets cancelled (client disconnects, timeout fires, parent context cancelled), but your handler ignores it and keeps consuming resources — CPU, memory, database connections.
In a high-traffic service, these resource leaks compound. Eventually, you run out of file descriptors or memory, and the service crashes under load.
CodeHawk checks for loops and background operations that ignore context.
3. Race Conditions in Shared State
Go's race detector is excellent, but it only catches races you actually test. In code review, races are invisible unless you're specifically looking for them.
// Vulnerable — data race on shared map
type Cache struct {
data map[string]string
}
func (c *Cache) Get(key string) string {
return c.data[key] // Read without lock
}
func (c *Cache) Set(key, value string) {
c.data[key] = value // Write without lock
}
// Multiple goroutines can call Get/Set simultaneously
// → Race condition on c.data map
// CodeHawk flags it: Unsynchronized access to shared state
// Safe: Protect with mutex
type Cache struct {
mu sync.RWMutex
data map[string]string
}
func (c *Cache) Get(key string) string {
c.mu.RLock()
defer c.mu.RUnlock()
return c.data[key]
}
func (c *Cache) Set(key, value string) {
c.mu.Lock()
defer c.mu.Unlock()
c.data[key] = value
}
Race conditions are the worst production bugs: they're non-deterministic, hard to reproduce, and cause mysterious crashes or data corruption. They're also completely invisible in code review unless you specifically look for concurrent access patterns.
CodeHawk checks for shared state (maps, slices) being accessed from multiple goroutines without synchronization.
4. SQL Injection in Database Queries
Like every language, Go developers occasionally concatenate user input into SQL.
// Vulnerable — string concatenation in SQL
func GetUser(id string) (*User, error) {
query := "SELECT * FROM users WHERE id = '" + id + "'"
row := db.QueryRow(query)
// ...
}
// CodeHawk flags it: String concatenation in SQL
// Safe: Use parameterized queries
func GetUser(id string) (*User, error) {
query := "SELECT * FROM users WHERE id = ?"
row := db.QueryRow(query, id)
// ...
}
Go's database/sql package makes this easy — parameterized queries are the default pattern. But under pressure, developers still write concatenation, especially in generated code or migrations.
CodeHawk catches this pattern.
5. Unhandled Errors in Defer Blocks
Go's error handling is explicit (which is good), but easy to forget in defer blocks (which is bad).
// Vulnerable — error in defer block is ignored
func ProcessFile(filename string) error {
f, err := os.Open(filename)
if err != nil {
return err
}
defer f.Close() // Close error is ignored
// Read and process file
return nil
}
// CodeHawk flags it: Error from defer Close() not handled
// Safe: Wrap defer to capture error
func ProcessFile(filename string) error {
f, err := os.Open(filename)
if err != nil {
return err
}
defer func() {
if err := f.Close(); err != nil {
// Handle error: log it, or wrap with original error
log.Printf("Close error: %v", err)
}
}()
// Read and process file
return nil
}
Forgetting to handle errors in defer blocks is easy — defer f.Close() looks clean, and you assume it can't fail. But Close() can fail (disk full on final flush, broken pipe on network socket, etc.). Ignoring these errors causes silent data loss.
CodeHawk checks for error-returning functions in defer blocks that don't capture and check the error.
Implementing Automated Code Review for Go Teams
Step 1: Get Access
Install CodeHawk at github.com/apps/codehawk-crossgen — it's a GitHub App that starts reviewing PRs immediately on your org — select your repos and it starts reviewing PRs automatically.
Step 2: Combine with Existing Tooling
Go teams typically use:
- go fmt for formatting (linting via gofmt)
- go vet for static analysis
- golangci-lint for comprehensive linting
- gosec for security scanning
CodeHawk complements these:
- go vet catches obvious mistakes; CodeHawk catches context/concurrency patterns
- golangci-lint catches style issues; CodeHawk catches semantic bugs
- gosec catches known vulnerability patterns; CodeHawk adds Claude-level analysis
Step 3: Monitor for Concurrency Issues
After the first week, review what CodeHawk flagged:
- Did it catch actual concurrency bugs?
- Are there false positives (code you've verified is safe)?
- What patterns are most valuable to your team?
Use this to calibrate how your team handles CodeHawk flags in review.
Real-World Example: Go HTTP Handler with Multiple Issues
// A typical Go HTTP handler with several bugs CodeHawk would catch
type Server struct {
cache map[string]interface{}
}
// Issue 1: Unhandled goroutine panic
func (s *Server) fetchFromCache(key string) {
go func() {
data := s.cache[key] // Issue 2: Race condition - no lock
processData(data) // Could panic, no recovery
}()
}
// Issue 3: SQL injection risk
func (s *Server) getUserByID(userID string) (*User, error) {
query := fmt.Sprintf("SELECT * FROM users WHERE id = '%s'", userID)
// ...
}
// Issue 4: Context cancellation ignored
func (s *Server) HandleRequest(ctx context.Context, w http.ResponseWriter, r *http.Request) {
for i := 0; i < 1000; i++ {
// No check for ctx.Done()
s.processItem(ctx) // Ignores context cancellation
}
// Issue 5: Deferred file close without error handling
file, _ := os.Open("data.txt")
defer file.Close() // Error ignored
w.WriteHeader(http.StatusOK)
}
CodeHawk would flag all five:
- Goroutine without panic recovery — line 6
- Race condition on shared map — line 7
- SQL injection via string formatting — line 12
- Context cancellation ignored in loop — line 20
- Unhandled error in defer — line 25
For Go Teams Shipping Concurrent Services
CodeHawk is especially valuable for Go because:
- Concurrency bugs are hard to find by inspection — race detector only catches bugs you test
- Context handling is critical in high-traffic services — easy to forget in loops
- Error handling is explicit but easy to forget — defer blocks, goroutine returns
The combination of Go's concurrency model + explicit error handling + pattern-based static analysis gives you a strong safety net.
Next Steps
- Install CodeHawk at github.com/apps/codehawk-crossgen
- Open a PR with Go code changes once access is granted
- Review concurrency and error handling — especially valuable for Go teams shipping concurrent services
The goal: catch the bugs that make you page on-call at 3am, before they make it to production.