The trap

Warning

Closed channels never block in a select — they fire immediately with the zero value. For error channels, zero is nil, which looks like success.

// ❌ Wrong — closed channel sends nil, treated as unknown error
select {
case err := <-response.Errors:
    if err != nil {
        return nil, fmt.Errorf("failed: %w", err)
    }
    return nil, fmt.Errorf("unknown error") // fires on close
}

The fix

Two-value receive distinguishes a real value from channel closure.

// ✅ Correct
select {
case err, ok := <-response.Errors:
    if !ok {
        continue // channel closed, not an error
    }
    if err != nil {
        return nil, fmt.Errorf("failed: %w", err)
    }
}

Tip

Key rules

  • ok == false → channel is closed
  • Closed channels in a select loop are selected repeatedly — handle with continue, break, or set the channel to nil to stop selecting it
  • Critical when using streaming APIs (e.g. OpenFGA’s StreamedListObjects) where multiple channels signal completion, errors, and data