The trap
Warning
Closed channels never block in a
select— they fire immediately with the zero value. Forerrorchannels, zero isnil, 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 tonilto stop selecting it- Critical when using streaming APIs (e.g. OpenFGA’s
StreamedListObjects) where multiple channels signal completion, errors, and data