╭ changed ─────────────────╮ │src/serve_ssh_protocol.zig│ ╰──────────────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │403 try writeU32(&close, self.allocator, self.channel.remote_id); │ │404 try self.sc_cipher.writePacket(self.io, self.allocator, self.writer, close.items); │ │405 │ │406 - // wait for the peer's CLOSE (or EOF) before letting the caller close │ │407 - // the socket. closing while the peer still has data in flight makes │ │408 - // Linux send RST instead of FIN, which surfaces as "Connection reset │ │409 - // by peer" on the client and can truncate the last sideband bytes. │ │406 + // read and discard until the peer closes (TCP FIN) before letting the │ │407 + // caller close the socket. a git client, after our CHANNEL_CLOSE, still │ │408 + // sends its own CHANNEL_CLOSE and SSH_MSG_DISCONNECT before closing; if │ │409 + // we close with those unread, Linux sends RST instead of FIN, which the │ │410 + // client reports as "Connection reset by peer" and treats as a failed │ │411 + // push even though the ref updated. draining to EOF leaves nothing │ │412 + // unread, so we send a clean FIN. we're tearing down, so the packet │ │413 + // contents don't matter — just get them off the socket. │ │414 var drain_packets: u32 = 0; │ │411 - while (!self.incoming_eof and drain_packets < max_exit_drain_packets) : (drain_packets += 1) {│ │412 - self.processOneBackgroundPacket() catch break; │ │415 + while (drain_packets < max_exit_drain_packets) : (drain_packets += 1) { │ │416 + const packet = self.cs_cipher.readPacket(self.allocator, self.reader) catch break; │ │417 + self.allocator.free(packet); │ │418 } │ │419 } │ │420 }; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────╯