EpicGames/lore · error

Failed to write header

Error message

Failed to write header

What it means

This panic comes from the test helper request(): SendStream::write returned Err and the harness expects it with this message. Writing to a QUIC send stream fails when the connection has been closed, the stream has been stopped/reset by the peer, or the send window cannot be advanced.

Solutions

  1. Inspect the returned WriteError (ClosedStream vs ConnectionLost vs Stopped) to pick the fix
  2. Ensure the server handler keeps the recv stream open until it has read the full header
  3. Check the server didn't close the connection due to a handler panic (check server logs/JoinHandles)
  4. Reorder the test so request() is called before any step that stops or finishes the stream

Example fix

// before
send.write(&CommandHeader::new(behaviour.opcode(), command_id, 0).to_bytes()).await.expect("Failed to write header");
// after
if let Err(e) = send.write(&CommandHeader::new(behaviour.opcode(), command_id, 0).to_bytes()).await {
    panic!("Failed to write header: {:?}, connection reason: {:?}", e, connection.close_reason());
}
Defensive patterns

Strategy: try-catch

Validate before calling

// ensure the stream is still writable
if harness.connection.close_reason().is_some() {
    panic!("cannot write header: connection closed");
}

Type guard

fn stream_writable(conn: &quinn::Connection) -> bool { conn.close_reason().is_none() }

Try / catch

match send.write(&header.to_bytes()).await {
    Ok(_) => (),
    Err(quinn::WriteError::Stopped(code)) => panic!("peer stopped stream: {code}"),
    Err(e) => panic!("Failed to write header: {e:?}"),
}

Prevention

When it happens

Trigger: Calling send.write() on a stream whose connection was closed by the peer, after recv-side issued StopSending causing the stream to reset, or after writing to an already-finished stream.

Common situations: Server handler called stop/reset on the stream before reading the header; server process dropped the connection mid-test; test writes a header after the handler already returned and closed the stream.

Related errors


AI-assisted analysis of EpicGames/lore@074eb0b0d1 (2026-09-13). Data as JSON: /api/errors/8cffbd6c0a70a6c8. Report an issue: GitHub.

Appendix: source

Thrown at lore-server/src/quic/stream_handler.rs:849

        _server: QuinnServer,
        _endpoint: Endpoint,
    }

    impl Harness {
        /// Open an additional stream on the same connection.
        async fn open_stream(&self) -> (SendStream, RecvStream) {
            self.connection
                .open_bi()
                .await
                .expect("Failed to open additional stream")
        }
    }

    /// Send a request asking the handler for `behaviour`.
    async fn request(send: &mut SendStream, behaviour: MockBehaviour, command_id: u32) {
        send.write(&CommandHeader::new(behaviour.opcode(), command_id, 0).to_bytes())
            .await
            .expect("Failed to write header");
        send.flush().await.expect("Failed flush");
    }

    /// Read one response header.
    async fn response(recv: &mut RecvStream) -> CommandHeader {
        let mut buffer = [0u8; 8];
        recv.read_exact(&mut buffer)
            .await
            .expect("Failed to read response");
        CommandHeader::from_bytes(&buffer)
    }

    /// Serve `factory` for `protocol` on a loopback endpoint and open a client stream to it.
    ///
    /// The client skips certificate verification and presents none of its own; the mTLS tests
    /// build their endpoints by hand because varying exactly that is what they test.
    async fn serve_and_connect(
        factory: Box<dyn StreamHandlerFactory>,

View on GitHub (pinned to 074eb0b0d1)