{"record":{"id":"92ccc0d14a5d2d3b","repo":"ethereum/go-ethereum","slug":"channel-argument-of-subscribe-has-type-t-need-wr","errorCode":null,"errorMessage":"channel argument of Subscribe has type %T, need writable channel","messagePattern":"channel argument of Subscribe has type %T, need writable channel","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"rpc/client.go","lineNumber":508,"sourceCode":"}\n\n// Subscribe calls the \"<namespace>_subscribe\" method with the given arguments,\n// registering a subscription. Server notifications for the subscription are\n// sent to the given channel. The element type of the channel must match the\n// expected type of content returned by the subscription.\n//\n// The context argument cancels the RPC request that sets up the subscription but has no\n// effect on the subscription after Subscribe has returned.\n//\n// Slow subscribers will be dropped eventually. Client buffers up to 20000 notifications\n// before considering the subscriber dead. The subscription Err channel will receive\n// ErrSubscriptionQueueOverflow. Use a sufficiently large buffer on the channel or ensure\n// that the channel usually has at least one reader to prevent this issue.\nfunc (c *Client) Subscribe(ctx context.Context, namespace string, channel interface{}, args ...interface{}) (*ClientSubscription, error) {\n\t// Check type of channel first.\n\tchanVal := reflect.ValueOf(channel)\n\tif chanVal.Kind() != reflect.Chan || chanVal.Type().ChanDir()&reflect.SendDir == 0 {\n\t\tpanic(fmt.Sprintf(\"channel argument of Subscribe has type %T, need writable channel\", channel))\n\t}\n\tif chanVal.IsNil() {\n\t\tpanic(\"channel given to Subscribe must not be nil\")\n\t}\n\tif c.isHTTP {\n\t\treturn nil, ErrNotificationsUnsupported\n\t}\n\n\tmsg, err := c.newMessage(namespace+subscribeMethodSuffix, args...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\top := &requestOp{\n\t\tids:  []json.RawMessage{msg.ID},\n\t\tresp: make(chan []*jsonrpcMessage, 1),\n\t\tsub:  newClientSubscription(c, namespace, chanVal),\n\t}\n","sourceCodeStart":490,"sourceCodeEnd":526,"githubUrl":"https://github.com/ethereum/go-ethereum/blob/6bb0588ad8e7f922e4ad5580f51265a4097af08f/rpc/client.go#L490-L526","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nvar ch <-chan types.Header\nsub, err := client.Subscribe(ctx, \"eth\", ch) // panics\n\n// after\nch := make(chan types.Header)\nsub, err := client.Subscribe(ctx, \"eth\", ch)\ndefer sub.Unsubscribe()\nfor h := range ch { fmt.Println(h.Number) }","handlingStrategy":"type-guard","validationCode":"func checkWritableChannel(v interface{}) error {\n    rv := reflect.ValueOf(v)\n    if rv.Kind() != reflect.Chan {\n        return fmt.Errorf(\"need a channel, got %T\", v)\n    }\n    if rv.IsNil() {\n        return errors.New(\"channel must not be nil\")\n    }\n    if rv.Type().ChanDir()&reflect.SendDir == 0 {\n        return fmt.Errorf(\"channel %T is receive-only\", v)\n    }\n    return nil\n}\n// before Subscribe:\nif err := checkWritableChannel(ch); err != nil { return nil, err }","typeGuard":"func isWritableChannel(v interface{}) bool {\n    rv := reflect.ValueOf(v)\n    return rv.Kind() == reflect.Chan && !rv.IsNil() &&\n        rv.Type().ChanDir()&reflect.SendDir != 0\n}","tryCatchPattern":"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'.","preventionTips":["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."],"tags":["rpc","subscribe","pubsub","channels","reflection","panic"],"backgroundTag":null,"analyzedSha":"6bb0588ad8e7f922e4ad5580f51265a4097af08f","analyzedAt":"2026-08-15T10:06:53.996Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}