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:

CodeHawk complements these:

Step 3: Monitor for Concurrency Issues

After the first week, review what CodeHawk flagged:

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:

  1. Goroutine without panic recovery — line 6
  2. Race condition on shared map — line 7
  3. SQL injection via string formatting — line 12
  4. Context cancellation ignored in loop — line 20
  5. Unhandled error in defer — line 25

For Go Teams Shipping Concurrent Services

CodeHawk is especially valuable for Go because:

The combination of Go's concurrency model + explicit error handling + pattern-based static analysis gives you a strong safety net.

Next Steps

The goal: catch the bugs that make you page on-call at 3am, before they make it to production.