b5991a8d82
The SSH executor's port-forwarder previously called the full Close() as soon as either direction of the bidirectional copy finished. Under HTTP-Upgrade WebSocket traffic this meant: when the WS server flushed its final frame and shut down the write side, our local->remote io.Copy returned EOF, we closed the entire SSH channel, the remote->local copy aborted mid-frame, and the backend's WS reader observed close 1006 (unexpected EOF) instead of a clean close frame. Switch to a half-close pattern: each direction CloseWrite()s its destination when its source EOFs, letting the other half drain the remaining frames. The final Close happens after both copies return. The "ssh forwarder dial remote failed: EOF" warning in e2e logs was a follow-on symptom — the SSH client was being torn down by the abnormal close, so any later channel-open returned EOF immediately.