ethereum/go-ethereum · error
channel argument of Subscribe has type %T, need writable cha
Error message
channel argument of Subscribe has type %T, need writable channel
What it means
rpc.Client.Subscribe requires the third argument to be a writable (bidirectional or send-only) Go channel on which notifications are delivered. The method reflects on the argument at setup time and panics if it is not a channel at all, or is receive-only — since the client could never deliver notifications into it.
Source
Thrown at rpc/client.go:508
}
// Subscribe calls the "<namespace>_subscribe" method with the given arguments,
// registering a subscription. Server notifications for the subscription are
// sent to the given channel. The element type of the channel must match the
// expected type of content returned by the subscription.
//
// The context argument cancels the RPC request that sets up the subscription but has no
// effect on the subscription after Subscribe has returned.
//
// Slow subscribers will be dropped eventually. Client buffers up to 20000 notifications
// before considering the subscriber dead. The subscription Err channel will receive
// ErrSubscriptionQueueOverflow. Use a sufficiently large buffer on the channel or ensure
// that the channel usually has at least one reader to prevent this issue.
func (c *Client) Subscribe(ctx context.Context, namespace string, channel interface{}, args ...interface{}) (*ClientSubscription, error) {
// Check type of channel first.
chanVal := reflect.ValueOf(channel)
if chanVal.Kind() != reflect.Chan || chanVal.Type().ChanDir()&reflect.SendDir == 0 {
panic(fmt.Sprintf("channel argument of Subscribe has type %T, need writable channel", channel))
}
if chanVal.IsNil() {
panic("channel given to Subscribe must not be nil")
}
if c.isHTTP {
return nil, ErrNotificationsUnsupported
}
msg, err := c.newMessage(namespace+subscribeMethodSuffix, args...)
if err != nil {
return nil, err
}
op := &requestOp{
ids: []json.RawMessage{msg.ID},
resp: make(chan []*jsonrpcMessage, 1),
sub: newClientSubscription(c, namespace, chanVal),
}
View on GitHub (pinned to 6bb0588ad8)
Solutions
- Allocate a fresh bidirectional channel: ch := make(chan types.Header) and pass ch directly.
- Keep the subscription's channel direction as send-capable in your wrapper signatures (use 'chan T', never '<-chan T').
- Validate with reflection before calling Subscribe when the channel comes from user/plugin input.
Example fix
// before
var ch <-chan types.Header
sub, err := client.Subscribe(ctx, "eth", ch) // panics
// after
ch := make(chan types.Header)
sub, err := client.Subscribe(ctx, "eth", ch)
defer sub.Unsubscribe()
for h := range ch { fmt.Println(h.Number) } Defensive patterns
Strategy: type-guard
Validate before calling
func checkWritableChannel(v interface{}) error {
rv := reflect.ValueOf(v)
if rv.Kind() != reflect.Chan {
return fmt.Errorf("need a channel, got %T", v)
}
if rv.IsNil() {
return errors.New("channel must not be nil")
}
if rv.Type().ChanDir()&reflect.SendDir == 0 {
return fmt.Errorf("channel %T is receive-only", v)
}
return nil
}
// before Subscribe:
if err := checkWritableChannel(ch); err != nil { return nil, err } Type guard
func isWritableChannel(v interface{}) bool {
rv := reflect.ValueOf(v)
return rv.Kind() == reflect.Chan && !rv.IsNil() &&
rv.Type().ChanDir()&reflect.SendDir != 0
} Try / catch
Prefer pre-validation; if wrapping third-party calls, defer/recover the panic and report the offending type from the message 'channel argument of Subscribe has type %T'.
Prevention
- Allocate channels with make(chan T, N) at the Subscribe call site.
- Declare wrapper parameters as 'chan T', never '<-chan T'.
- Buffer the channel (e.g. 20000) or keep a dedicated reader to avoid the separate queue-overflow drop.
When it happens
Trigger: Calling client.Subscribe(ctx, "eth", ch, args...) where ch is a non-channel value or a receive-only channel (e.g. passing a <-chan type field or a value obtained from another subscription). A subsequent check also panics on nil channels.
Common situations: Passing a channel stored in a struct as receive-only; forwarding subscription output by passing the receive side of a pipe; dynamically building Subscribe args and losing channel direction; passing a nil interface holding a typed nil channel.
Related errors
- can't create multiple subscriptions with Notifier
- channel given to Subscribe must not be nil
- nil TextMapPropagator configured
- nil auth
- can't create subscription after subscribe call has returned
AI-assisted analysis of ethereum/go-ethereum@6bb0588ad8 (2026-08-15).
Data as JSON: /api/errors/92ccc0d14a5d2d3b.
Report an issue: GitHub.