One is the ownership model. You can close a channel, and sends will then panic (there's no way to check if a channel is open), so ownership is always in the hands of the code that writes to the channel. You can -- and must, if you ever want multiple producers with a single channel -- abstract ownership into a wrapper that keeps the channel and a tracks whether the channel has been closed, but it's tedious to implement it over and over (it needs to be thread safe, too).
It's tempting to use channels as part of a public interface (between two cleanly separated packages), but this nearly always is a bad idea. Channels just don't compose well as an iteration construct, and you encounter ugly surprises such as realizing the caller is using an unbuffered channel causing stuff to block. It's almost always better to offer a callback interface (push) or an iterator interface (pull), and then either side can use channels internally, or not at all, as needed.
In practice, it's sometimes surprisingly hard to avoid goroutine leakage with channels. It's a simple mechanism that gets complicated fast, especially as you combine them with mutexes and error handling (though ErrorGroup is a timesaver that ought to be part of the standard library).
And finally, the behaviour of nil channels (they block forever!) is unfortunate. It is touted as a feature, but for me it has been exclusively the source of surprising bugs.