Okay, so the issue that I see here is that you expect the user to close the handle immediately on error, else handle->io_watcher.fd wouldn't be -1.

It's not a terrible assumption but I don't think it's always going to be true.  If the handle is still open, execution falls through and what happens next depends entirely on the value of the events argument.

I think the way to address that is by having uv__stream_connect() return a status code and act on that.