Keepalive is gated on downstream idle, not upstream tick cadence. The old fixture wrote progress events every 250ms and could finish without a true 1s idle window on loaded CI runners, so ":\n\n" never appeared. Pause after preamble long enough for the keepalive ticker before completing the stream.